Index: libedataserver/e-iconv.c
===================================================================
--- libedataserver/e-iconv.c	(revision 409)
+++ libedataserver/e-iconv.c	(working copy)
@@ -26,6 +26,8 @@
 #include <config.h>
 #endif
 
+#define ICONV_10646 "iso-10646" 
+
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
Index: addressbook/libebook-dbus/e-address-western.h
===================================================================
--- addressbook/libebook-dbus/e-address-western.h	(revision 409)
+++ addressbook/libebook-dbus/e-address-western.h	(working copy)
@@ -1 +1,25 @@
-link ../libebook-orbit/./e-address-western.h
\ No newline at end of file
+#ifndef __E_ADDRESS_WESTERN_H__
+#define __E_ADDRESS_WESTERN_H__
+
+G_BEGIN_DECLS
+
+typedef struct {
+	
+	/* Public */
+	char *po_box;
+	char *extended;  /* I'm not sure what this is. */
+	char *street;
+	char *locality;  /* For example, the city or town. */
+	char *region;	/* The state or province. */
+	char *postal_code;
+	char *country;
+} EAddressWestern;
+
+EAddressWestern *e_address_western_parse (const char *address);
+void e_address_western_free (EAddressWestern *eaw);
+
+G_END_DECLS
+
+#endif  /* ! __E_ADDRESS_WESTERN_H__ */
+
+
Index: addressbook/libebook-dbus/e-destination.c
===================================================================
--- addressbook/libebook-dbus/e-destination.c	(revision 409)
+++ addressbook/libebook-dbus/e-destination.c	(working copy)
@@ -39,17 +39,13 @@
 #include <stdlib.h>
 #include <ctype.h>
 #include <string.h>
-#include "e-book.h"
+#include <libebook/e-book.h>
 #include <libedataserver/e-data-server-marshal.h>
 
 #include <glib.h>
 #include <libxml/xmlmemory.h>
 #include <glib/gi18n-lib.h>
-#if ENABLE_CAMEL
 #include <camel/camel-internet-address.h>
-#else
-#include <ucamel/camel-internet-address.h>
-#endif
 
 #define d(x)
 
@@ -1126,34 +1122,34 @@
 	
 	g_return_val_if_fail (dest && E_IS_DESTINATION (dest), NULL);
 	
-	dest_node = xmlNewNode (NULL, (xmlChar*)"destination");
+	dest_node = xmlNewNode (NULL, "destination");
 	
 	str = e_destination_get_name (dest);
 	if (str)
-		xmlNewTextChild (dest_node, NULL, (xmlChar*)"name", (xmlChar*)str);
+		xmlNewTextChild (dest_node, NULL, "name", str);
 	
 	if (!e_destination_is_evolution_list (dest)) {
 		str = e_destination_get_email (dest);
 		if (str)
-			xmlNewTextChild (dest_node, NULL, (xmlChar*)"email", (xmlChar*)str);
+			xmlNewTextChild (dest_node, NULL, "email", str);
 	} else {
 		GList *iter = dest->priv->list_dests;
 		
 		while (iter) {
 			EDestination *list_dest = E_DESTINATION (iter->data);
-			xmlNodePtr list_node = xmlNewNode (NULL, (xmlChar*)"list_entry");
+			xmlNodePtr list_node = xmlNewNode (NULL, "list_entry");
 
 			str = e_destination_get_name (list_dest);
 			if (str) {
-				xmlChar *escaped = xmlEncodeEntitiesReentrant (NULL, (xmlChar*)str);
-				xmlNewTextChild (list_node, NULL, (xmlChar*)"name", escaped);
+				char *escaped = xmlEncodeEntitiesReentrant (NULL, str);
+				xmlNewTextChild (list_node, NULL, "name", escaped);
 				xmlFree (escaped);
 			}
 			
 			str = e_destination_get_email (list_dest);
 			if (str) {
-				xmlChar *escaped = xmlEncodeEntitiesReentrant (NULL, (xmlChar*)str);
-				xmlNewTextChild (list_node, NULL, (xmlChar*)"email", escaped);
+				char *escaped = xmlEncodeEntitiesReentrant (NULL, str);
+				xmlNewTextChild (list_node, NULL, "email", str);
 				xmlFree (escaped);
 			}
 			
@@ -1162,15 +1158,15 @@
 			iter = g_list_next (iter);
 		}
 		
-		xmlNewProp (dest_node, (xmlChar*)"is_list", (xmlChar*)"yes");
-		xmlNewProp (dest_node, (xmlChar*)"show_addresses", 
-			    e_destination_list_show_addresses (dest) ? (xmlChar*)"yes" : (xmlChar*)"no");
+		xmlNewProp (dest_node, "is_list", "yes");
+		xmlNewProp (dest_node, "show_addresses", 
+			    e_destination_list_show_addresses (dest) ? "yes" : "no");
 	}
 	
 	str = e_destination_get_source_uid (dest);
 	if (str) {
-		xmlChar *escaped = xmlEncodeEntitiesReentrant (NULL, (xmlChar*)str);
-		xmlNewTextChild (dest_node, NULL, (xmlChar*)"source_uid", escaped);
+		char *escaped = xmlEncodeEntitiesReentrant (NULL, str);
+		xmlNewTextChild (dest_node, NULL, "source_uid", str);
 		xmlFree (escaped);
 	}
 	
@@ -1178,15 +1174,15 @@
 	if (str) {
 		char buf[16];
 		
-		xmlNodePtr uri_node = xmlNewTextChild (dest_node, NULL, (xmlChar*)"card_uid", (xmlChar*)str);
+		xmlNodePtr uri_node = xmlNewTextChild (dest_node, NULL, "card_uid", str);
 		g_snprintf (buf, 16, "%d", e_destination_get_email_num (dest));
-		xmlNewProp (uri_node, (xmlChar*)"email_num", (xmlChar*)buf);
+		xmlNewProp (uri_node, "email_num", buf);
 	}
 	
-	xmlNewProp (dest_node, (xmlChar*)"html_mail", e_destination_get_html_mail_pref (dest) ? (xmlChar*)"yes" : (xmlChar*)"no");
+	xmlNewProp (dest_node, "html_mail", e_destination_get_html_mail_pref (dest) ? "yes" : "no");
 	
-	xmlNewProp (dest_node, (xmlChar*)"auto_recipient",
-		    e_destination_is_auto_recipient (dest) ? (xmlChar*)"yes" : (xmlChar*)"no");
+	xmlNewProp (dest_node, "auto_recipient",
+		    e_destination_is_auto_recipient (dest) ? "yes" : "no");
 	
 	return dest_node;
 }
@@ -1214,28 +1210,28 @@
 	g_return_val_if_fail (dest && E_IS_DESTINATION (dest), FALSE);
 	g_return_val_if_fail (node != NULL, FALSE);
 	
-	if (strcmp ((char*)node->name, "destination"))
+	if (strcmp (node->name, "destination"))
 		return FALSE;
 	
-	tmp = (char*)xmlGetProp (node, (xmlChar*)"html_mail");
+	tmp = xmlGetProp (node, "html_mail");
 	if (tmp) {
 		html_mail = !strcmp (tmp, "yes");
 		xmlFree (tmp);
 	}
 	
-	tmp = (char*)xmlGetProp (node, (xmlChar*)"is_list");
+	tmp = xmlGetProp (node, "is_list");
 	if (tmp) {
 		is_list = !strcmp (tmp, "yes");
 		xmlFree (tmp);
 	}
 	
-	tmp = (char*)xmlGetProp (node, (xmlChar*)"show_addresses");
+	tmp = xmlGetProp (node, "show_addresses");
 	if (tmp) {
 		show_addr = !strcmp (tmp, "yes");
 		xmlFree (tmp);
 	}
 	
-	tmp = (char*)xmlGetProp (node, (xmlChar*)"auto_recipient");
+	tmp = xmlGetProp (node, "auto_recipient");
 	if (tmp) {
 		auto_recip = !strcmp (tmp, "yes");
 		xmlFree (tmp);
@@ -1243,28 +1239,28 @@
 	
 	node = node->xmlChildrenNode;
 	while (node) {
-		if (!strcmp ((char*)node->name, "name")) {
-			tmp = (char*)xmlNodeGetContent (node);
+		if (!strcmp (node->name, "name")) {
+			tmp = xmlNodeGetContent (node);
 			g_free (name);
 			name = g_strdup (tmp);
 			xmlFree (tmp);
-		} else if (!is_list && !strcmp ((char*)node->name, "email")) {
-			tmp = (char*)xmlNodeGetContent (node);
+		} else if (!is_list && !strcmp (node->name, "email")) {
+			tmp = xmlNodeGetContent (node);
 			g_free (email);
 			email = g_strdup (tmp);
 			xmlFree (tmp);
-		} else if (is_list && !strcmp ((char*)node->name, "list_entry")) {
+		} else if (is_list && !strcmp (node->name, "list_entry")) {
 			xmlNodePtr subnode = node->xmlChildrenNode;
 			char *list_name = NULL, *list_email = NULL;
 			
 			while (subnode) {
-				if (!strcmp ((char*)subnode->name, "name")) {
-					tmp = (char*)xmlNodeGetContent (subnode);
+				if (!strcmp (subnode->name, "name")) {
+					tmp = xmlNodeGetContent (subnode);
 					g_free (list_name);
 					list_name = g_strdup (tmp);
 					xmlFree (tmp);
-				} else if (!strcmp ((char*)subnode->name, "email")) {
-					tmp = (char*)xmlNodeGetContent (subnode);
+				} else if (!strcmp (subnode->name, "email")) {
+					tmp = xmlNodeGetContent (subnode);
 					g_free (list_email);
 					list_email = g_strdup (tmp);
 					xmlFree (tmp);
@@ -1286,18 +1282,18 @@
 				
 				list_dests = g_list_append (list_dests, list_dest);
 			}
-		} else if (!strcmp ((char*)node->name, "source_uid")) {
-			tmp = (char*)xmlNodeGetContent (node);
+		} else if (!strcmp (node->name, "source_uid")) {
+			tmp = xmlNodeGetContent (node);
 			g_free (source_uid);
 			source_uid = g_strdup (tmp);
 			xmlFree (tmp);
-		} else if (!strcmp ((char*)node->name, "card_uid")) {
-			tmp = (char*)xmlNodeGetContent (node);
+		} else if (!strcmp (node->name, "card_uid")) {
+			tmp = xmlNodeGetContent (node);
 			g_free (card_uid);
 			card_uid = g_strdup (tmp);
 			xmlFree (tmp);
 			
-			tmp = (char*)xmlGetProp (node, (xmlChar*)"email_num");
+			tmp = xmlGetProp (node, "email_num");
 			email_num = atoi (tmp);
 			xmlFree (tmp);
 		}
@@ -1345,7 +1341,7 @@
 	if (xml_in == NULL || size <= 0) 
 		return NULL;
 	
-	xml = g_strndup ((char*)xml_in, size);
+	xml = g_strndup (xml_in, size);
 	r = w = xml;
 	
 	while (*r) {
@@ -1393,7 +1389,7 @@
 	if (dest_node == NULL)
 		return NULL;
 	
-	dest_doc = xmlNewDoc ((xmlChar*)XML_DEFAULT_VERSION);
+	dest_doc = xmlNewDoc (XML_DEFAULT_VERSION);
 	xmlDocSetRootElement (dest_doc, dest_node);
 	
 	xmlDocDumpMemory (dest_doc, &buffer, &size);
@@ -1455,8 +1451,8 @@
 	if (destv == NULL || *destv == NULL)
 		return NULL;
 	
-	destv_doc  = xmlNewDoc ((xmlChar*)XML_DEFAULT_VERSION);
-	destv_node = xmlNewNode (NULL, (xmlChar*)"destinations");
+	destv_doc  = xmlNewDoc (XML_DEFAULT_VERSION);
+	destv_node = xmlNewNode (NULL, "destinations");
 	xmlDocSetRootElement (destv_doc, destv_node);
 	
 	for (i = 0; destv[i]; i++) {
@@ -1502,7 +1498,7 @@
 	
 	node = destv_doc->xmlRootNode;
 	
-	if (strcmp ((char*)node->name, "destinations"))
+	if (strcmp (node->name, "destinations"))
 		goto finished;
 	
 	node = node->xmlChildrenNode;
Index: addressbook/libebook-dbus/e-name-western-tables.h
===================================================================
--- addressbook/libebook-dbus/e-name-western-tables.h	(revision 409)
+++ addressbook/libebook-dbus/e-name-western-tables.h	(working copy)
@@ -1 +1,272 @@
-link ../libebook-orbit/./e-name-western-tables.h
\ No newline at end of file
+/* This file is generated by gen-western-table.py. DO NOT EDIT */
+static const char western_pfx_table[] = {
+  "mister\0"
+  "miss.\0"
+  "mr.\0"
+  "mrs.\0"
+  "ms.\0"
+  "miss\0"
+  "mr\0"
+  "mrs\0"
+  "ms\0"
+  "sir\0"
+  "professor\0"
+  "prof.\0"
+  "dr\0"
+  "dr.\0"
+  "doctor\0"
+  "judge\0"
+  "justice\0"
+  "chief justice\0"
+  "congressman\0"
+  "congresswoman\0"
+  "commander\0"
+  "lieutenant\0"
+  "lt.\0"
+  "colonel\0"
+  "col.\0"
+  "major\0"
+  "maj.\0"
+  "general\0"
+  "gen.\0"
+  "admiral\0"
+  "admr.\0"
+  "sergeant\0"
+  "sgt.\0"
+  "lord\0"
+  "lady\0"
+  "baron\0"
+  "baroness\0"
+  "duke\0"
+  "duchess\0"
+  "king\0"
+  "queen\0"
+  "prince\0"
+  "princess\0"
+  "the most honorable\0"
+  "the honorable\0"
+  "the reverend\0"
+  "his holiness\0"
+  "his eminence\0"
+  "his majesty\0"
+  "her majesty\0"
+  "his grace\0"
+  "her grace\0"
+  "president\0"
+  "vice president\0"
+  "secretary\0"
+  "undersecretary\0"
+  "consul\0"
+  "ambassador\0"
+  "senator\0"
+  "saint\0"
+  "st.\0"
+  "pastor\0"
+  "deacon\0"
+  "father\0"
+  "bishop\0"
+  "archbishop\0"
+  "cardinal\0"
+  "pope\0"
+  "reverend\0"
+  "rev.\0"
+  "rabbi\0"
+  "monsieur\0"
+  "m.\0"
+  "mademoiselle\0"
+  "melle\0"
+  "madame\0"
+  "mme\0"
+  "professeur\0"
+  "dauphin\0"
+  "dauphine\0"
+  "herr\0"
+  "frau\0"
+  "fraulein\0"
+  "herr doktor\0"
+  "doktor frau\0"
+  "doktor frau doktor\0"
+  "frau doktor\0"
+  "senor\0"
+  "senora\0"
+  "sra.\0"
+  "senorita\0"
+  "srita.\0"
+};
+static const guint western_pfx_index[] = {
+  0,
+  7,
+  13,
+  17,
+  22,
+  26,
+  31,
+  34,
+  38,
+  41,
+  45,
+  55,
+  61,
+  64,
+  68,
+  75,
+  81,
+  89,
+  103,
+  115,
+  129,
+  139,
+  150,
+  154,
+  162,
+  167,
+  173,
+  178,
+  186,
+  191,
+  199,
+  205,
+  214,
+  219,
+  224,
+  229,
+  235,
+  244,
+  249,
+  257,
+  262,
+  268,
+  275,
+  284,
+  303,
+  317,
+  330,
+  343,
+  356,
+  368,
+  380,
+  390,
+  400,
+  410,
+  425,
+  435,
+  450,
+  457,
+  468,
+  476,
+  482,
+  486,
+  493,
+  500,
+  507,
+  514,
+  525,
+  534,
+  539,
+  548,
+  553,
+  559,
+  568,
+  571,
+  584,
+  590,
+  597,
+  601,
+  612,
+  620,
+  629,
+  634,
+  639,
+  648,
+  660,
+  672,
+  691,
+  703,
+  709,
+  716,
+  721,
+  730,
+};
+
+static const char western_sfx_table[] = {
+  "junior\0"
+  "senior\0"
+  "jr\0"
+  "sr\0"
+  "I\0"
+  "II\0"
+  "III\0"
+  "IV\0"
+  "V\0"
+  "VI\0"
+  "VII\0"
+  "VIII\0"
+  "IX\0"
+  "X\0"
+  "XI\0"
+  "XII\0"
+  "XIII\0"
+  "XIV\0"
+  "XV\0"
+  "XVI\0"
+  "XVII\0"
+  "XVIII\0"
+  "XIX\0"
+  "XX\0"
+  "XXI\0"
+  "XXII\0"
+  "phd\0"
+  "ms\0"
+  "md\0"
+  "esq\0"
+  "esq.\0"
+  "esquire\0"
+};
+static const guint western_sfx_index[] = {
+  0,
+  7,
+  14,
+  17,
+  20,
+  22,
+  25,
+  29,
+  32,
+  34,
+  37,
+  41,
+  46,
+  49,
+  51,
+  54,
+  58,
+  63,
+  67,
+  70,
+  74,
+  79,
+  85,
+  89,
+  92,
+  96,
+  101,
+  105,
+  108,
+  111,
+  115,
+  120,
+};
+
+static const char western_complex_last_table[] = {
+  "van\0"
+  "von\0"
+  "de\0"
+  "di\0"
+};
+static const guint western_complex_last_index[] = {
+  0,
+  4,
+  8,
+  11,
+};
+
Index: addressbook/libebook-dbus/e-destination.h
===================================================================
--- addressbook/libebook-dbus/e-destination.h	(revision 409)
+++ addressbook/libebook-dbus/e-destination.h	(working copy)
@@ -1 +1,127 @@
-link ../libebook-orbit/e-destination.h
\ No newline at end of file
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * e-destination.h
+ *
+ * Copyright (C) 2001-2004 Ximian, Inc.
+ *
+ * Authors: Jon Trowbridge <trow@ximian.com>
+ *          Chris Toshok <toshok@ximian.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ * 
+ * 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 Lesser General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA.
+ */
+
+#ifndef __E_DESTINATION_H__
+#define __E_DESTINATION_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include <libebook/e-contact.h>
+#include <libebook/e-book.h>
+#include <libxml/tree.h>
+
+#define E_TYPE_DESTINATION           (e_destination_get_type ())
+#define E_DESTINATION(o)             (G_TYPE_CHECK_INSTANCE_CAST ((o), E_TYPE_DESTINATION, EDestination))
+#define E_DESTINATION_CLASS(k)       (G_TYPE_CHECK_CLASS_CAST ((k), E_TYPE_DESTINATION, EDestinationClass))
+#define E_IS_DESTINATION(o)          (G_TYPE_CHECK_INSTANCE_TYPE ((o), E_TYPE_DESTINATION))
+#define E_IS_DESTINATION_CLASS(k)    (G_TYPE_CHECK_CLASS_TYPE ((k), E_TYPE_DESTINATION))
+#define E_DESTINATION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), E_TYPE_DESTINATION, EDestinationClass))
+
+typedef struct _EDestination EDestination;
+typedef struct _EDestinationClass EDestinationClass;
+
+struct _EDestinationPrivate;
+
+struct _EDestination {
+	GObject object;
+
+	struct _EDestinationPrivate *priv;
+};
+
+struct _EDestinationClass {
+	GObjectClass parent_class;
+
+	/* Signals */
+	void (* changed) (EDestination *destination);
+
+	/* Padding for future expansion */
+	void (*_ebook_reserved1) (void);
+	void (*_ebook_reserved2) (void);
+	void (*_ebook_reserved3) (void);
+	void (*_ebook_reserved4) (void);
+};
+
+GType e_destination_get_type (void);
+
+
+EDestination  *e_destination_new                (void);
+EDestination  *e_destination_copy               (const EDestination *);
+
+gboolean       e_destination_empty              (const EDestination *);
+gboolean       e_destination_equal              (const EDestination *a, const EDestination *b);
+
+/* for use with EDestinations that wrap a particular contact */
+void           e_destination_set_contact        (EDestination *, EContact *contact, int email_num);
+void           e_destination_set_contact_uid    (EDestination *dest, const char *uid, gint email_num);
+void           e_destination_set_book           (EDestination *, EBook *book);
+EContact      *e_destination_get_contact        (const EDestination *);
+const char    *e_destination_get_source_uid     (const EDestination *);
+const char    *e_destination_get_contact_uid    (const EDestination *);
+int            e_destination_get_email_num      (const EDestination *);
+
+/* for use with EDestinations built up from strings (not corresponding to contacts in a user's address books) */
+void           e_destination_set_name           (EDestination *, const char *name);
+void           e_destination_set_email          (EDestination *, const char *email);
+const char    *e_destination_get_name           (const EDestination *);  /* "Jane Smith" */
+const char    *e_destination_get_email          (const EDestination *);  /* "jane@assbarn.com" */
+const char    *e_destination_get_address        (const EDestination *);  /* "Jane Smith <jane@assbarn.com>" (or a comma-sep set of such for a list) */
+
+gboolean       e_destination_is_evolution_list   (const EDestination *);
+gboolean       e_destination_list_show_addresses (const EDestination *);
+const GList   *e_destination_list_get_dests      (const EDestination *);
+
+/* If true, they want HTML mail. */
+void           e_destination_set_html_mail_pref (EDestination *dest, gboolean flag);
+gboolean       e_destination_get_html_mail_pref (const EDestination *);
+
+/* used by the evolution composer to manage automatic recipients
+
+   XXX should probably be implemented using a more neutral/extensible
+   approach instead of a hardcoded evolution-only flag. */
+gboolean       e_destination_is_auto_recipient  (const EDestination *);
+void           e_destination_set_auto_recipient (EDestination *, gboolean value);
+
+/* parse out an EDestination (name/email, not contact) from a free form string. */
+void           e_destination_set_raw            (EDestination *, const char *free_form_string);
+
+/* generate a plain-text representation of an EDestination* or EDestination** */
+const char    *e_destination_get_textrep        (const EDestination *, gboolean include_email);  /* "Jane Smith" or "jane@assbarn.com" */
+char          *e_destination_get_textrepv       (EDestination **);
+
+/* XML export/import routines. */
+char          *e_destination_export             (const EDestination *);
+char          *e_destination_exportv            (EDestination **);
+EDestination  *e_destination_import             (const char *str);
+EDestination **e_destination_importv            (const char *str);
+
+/* EVCard "export" routines */
+void          e_destination_export_to_vcard_attribute   (EDestination *dest, EVCardAttribute *attr);
+
+void           e_destination_freev              (EDestination **);
+
+#endif /* __E_DESTINATION_H__ */
+
Index: addressbook/libebook-dbus/e-name-western-tables.h.in
===================================================================
--- addressbook/libebook-dbus/e-name-western-tables.h.in	(revision 409)
+++ addressbook/libebook-dbus/e-name-western-tables.h.in	(working copy)
@@ -1 +1,133 @@
-link ../libebook-orbit/e-name-western-tables.h.in
\ No newline at end of file
+western_pfx
+mister
+miss.
+mr.
+mrs.
+ms.
+miss
+mr
+mrs
+ms
+sir
+professor
+prof.
+dr
+dr.
+doctor
+judge
+justice
+chief justice
+congressman
+congresswoman
+commander
+lieutenant
+lt.
+colonel
+col.
+major
+maj.
+general
+gen.
+admiral
+admr.
+sergeant
+sgt.
+lord
+lady
+baron
+baroness
+duke
+duchess
+king
+queen
+prince
+princess
+the most honorable
+the honorable
+the reverend
+his holiness
+his eminence
+his majesty
+her majesty
+his grace
+her grace
+president
+vice president
+secretary
+undersecretary
+consul
+ambassador
+senator
+saint
+st.
+pastor
+deacon
+father
+bishop
+archbishop
+cardinal
+pope
+reverend
+rev.
+rabbi
+monsieur
+m.
+mademoiselle
+melle
+madame
+mme
+professeur
+dauphin
+dauphine
+herr
+frau
+fraulein
+herr doktor
+doktor frau
+doktor frau doktor
+frau doktor
+senor
+senora
+sra.
+senorita
+srita.
+
+western_sfx
+junior
+senior
+jr
+sr
+I
+II
+III
+IV
+V
+VI
+VII
+VIII
+IX
+X
+XI
+XII
+XIII
+XIV
+XV
+XVI
+XVII
+XVIII
+XIX
+XX
+XXI
+XXII
+phd
+ms
+md
+esq
+esq.
+esquire
+
+western_complex_last
+van
+von
+de
+di
Index: addressbook/libebook-dbus/e-contact.c
===================================================================
--- addressbook/libebook-dbus/e-contact.c	(revision 409)
+++ addressbook/libebook-dbus/e-contact.c	(working copy)
@@ -1 +1,2066 @@
-link ../libebook-orbit/./e-contact.c
\ No newline at end of file
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* e-contact.c
+ *
+ * Copyright (C) 2003 Ximian, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Chris Toshok (toshok@ximian.com)
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <glib/gi18n-lib.h>
+#include "e-contact.h"
+#include "e-book.h"
+#include "e-name-western.h"
+
+#define d(x)
+
+struct _EContactPrivate {
+	char *cached_strings [E_CONTACT_FIELD_LAST];
+};
+
+#define E_CONTACT_FIELD_TYPE_STRING       0x00000001   /* used for simple single valued attributes */
+/*E_CONTACT_FIELD_TYPE_FLOAT*/
+#define E_CONTACT_FIELD_TYPE_LIST         0x00000002   /* used for multivalued single attributes - the elements are of type char* */
+#define E_CONTACT_FIELD_TYPE_MULTI        0x00000004   /* used for multivalued attributes - the elements are of type EVCardAttribute */
+#define E_CONTACT_FIELD_TYPE_GETSET       0x00000008   /* used for attributes that need custom handling for getting/setting */
+#define E_CONTACT_FIELD_TYPE_STRUCT       0x00000010   /* used for structured types (N and ADR properties, in particular) */
+#define E_CONTACT_FIELD_TYPE_BOOLEAN      0x00000020   /* used for boolean types (WANTS_HTML) */
+
+#define E_CONTACT_FIELD_TYPE_SYNTHETIC    0x10000000   /* used when there isn't a corresponding vcard field (such as email_1) */
+#define E_CONTACT_FIELD_TYPE_LIST_ELEM    0x20000000   /* used when a synthetic attribute is a numbered list element */
+#define E_CONTACT_FIELD_TYPE_MULTI_ELEM   0x40000000   /* used when we're looking for the nth attribute where more than 1 can be present in the vcard */
+#define E_CONTACT_FIELD_TYPE_ATTR_TYPE    0x80000000   /* used when a synthetic attribute is flagged with a TYPE= that we'll be looking for */
+
+typedef struct {
+	guint32 t;
+
+	EContactField field_id;
+	const char *vcard_field_name;
+	const char *field_name;      /* non translated */
+	const char *pretty_name;     /* translated */
+	
+	gboolean read_only;
+
+	int list_elem;
+	const char *attr_type1;
+	const char *attr_type2;
+
+	void* (*struct_getter)(EContact *contact, EVCardAttribute *attribute);
+	void (*struct_setter)(EContact *contact, EVCardAttribute *attribute, void *data);
+
+	GType (*boxed_type_getter) (void);
+} EContactFieldInfo;
+
+static void* photo_getter (EContact *contact, EVCardAttribute *attr);
+static void photo_setter (EContact *contact, EVCardAttribute *attr, void *data);
+static void* fn_getter (EContact *contact, EVCardAttribute *attr);
+static void fn_setter (EContact *contact, EVCardAttribute *attr, void *data);
+static void* n_getter (EContact *contact, EVCardAttribute *attr);
+static void n_setter (EContact *contact, EVCardAttribute *attr, void *data);
+static void* adr_getter (EContact *contact, EVCardAttribute *attr);
+static void adr_setter (EContact *contact, EVCardAttribute *attr, void *data);
+static void* date_getter (EContact *contact, EVCardAttribute *attr);
+static void date_setter (EContact *contact, EVCardAttribute *attr, void *data);
+static void* cert_getter (EContact *contact, EVCardAttribute *attr);
+static void cert_setter (EContact *contact, EVCardAttribute *attr, void *data);
+
+#define STRING_FIELD(id,vc,n,pn,ro)  { E_CONTACT_FIELD_TYPE_STRING, (id), (vc), (n), (pn), (ro) }
+#define BOOLEAN_FIELD(id,vc,n,pn,ro)  { E_CONTACT_FIELD_TYPE_BOOLEAN, (id), (vc), (n), (pn), (ro) }
+#define LIST_FIELD(id,vc,n,pn,ro)      { E_CONTACT_FIELD_TYPE_LIST, (id), (vc), (n), (pn), (ro) }
+#define MULTI_LIST_FIELD(id,vc,n,pn,ro) { E_CONTACT_FIELD_TYPE_MULTI, (id), (vc), (n), (pn), (ro) }
+#define GETSET_FIELD(id,vc,n,pn,ro,get,set)    { E_CONTACT_FIELD_TYPE_STRING | E_CONTACT_FIELD_TYPE_GETSET, (id), (vc), (n), (pn), (ro), -1, NULL, NULL, (get), (set) }
+#define STRUCT_FIELD(id,vc,n,pn,ro,get,set,ty)    { E_CONTACT_FIELD_TYPE_STRUCT | E_CONTACT_FIELD_TYPE_GETSET, (id), (vc), (n), (pn), (ro), -1, NULL, NULL, (get), (set), (ty) }
+#define SYNTH_STR_FIELD(id,n,pn,ro)  { E_CONTACT_FIELD_TYPE_STRING | E_CONTACT_FIELD_TYPE_SYNTHETIC, (id), NULL, (n), (pn), (ro) }
+#define LIST_ELEM_STR_FIELD(id,vc,n,pn,ro,nm) { E_CONTACT_FIELD_TYPE_LIST_ELEM | E_CONTACT_FIELD_TYPE_SYNTHETIC | E_CONTACT_FIELD_TYPE_STRING, (id), (vc), (n), (pn), (ro), (nm) }
+#define MULTI_ELEM_STR_FIELD(id,vc,n,pn,ro,nm) { E_CONTACT_FIELD_TYPE_MULTI_ELEM | E_CONTACT_FIELD_TYPE_SYNTHETIC | E_CONTACT_FIELD_TYPE_STRING, (id), (vc), (n), (pn), (ro), (nm) }
+#define ATTR_TYPE_STR_FIELD(id,vc,n,pn,ro,at1,nth) { E_CONTACT_FIELD_TYPE_ATTR_TYPE | E_CONTACT_FIELD_TYPE_SYNTHETIC | E_CONTACT_FIELD_TYPE_STRING, (id), (vc), (n), (pn), (ro), (nth), (at1), NULL }
+#define ATTR_TYPE_GETSET_FIELD(id,vc,n,pn,ro,at1,nth,get,set) { E_CONTACT_FIELD_TYPE_ATTR_TYPE | E_CONTACT_FIELD_TYPE_GETSET, (id), (vc), (n), (pn), (ro), (nth), (at1), NULL, (get), (set) }
+#define ATTR2_TYPE_STR_FIELD(id,vc,n,pn,ro,at1,at2,nth) { E_CONTACT_FIELD_TYPE_ATTR_TYPE | E_CONTACT_FIELD_TYPE_SYNTHETIC | E_CONTACT_FIELD_TYPE_STRING, (id), (vc), (n), (pn), (ro), (nth), (at1), (at2) }
+#define ATTR_TYPE_STRUCT_FIELD(id,vc,n,pn,ro,at,get,set,ty) { E_CONTACT_FIELD_TYPE_ATTR_TYPE | E_CONTACT_FIELD_TYPE_SYNTHETIC | E_CONTACT_FIELD_TYPE_GETSET | E_CONTACT_FIELD_TYPE_STRUCT, (id), (vc), (n), (pn), (ro), 0, (at), NULL, (get), (set), (ty) }
+
+/* This *must* be kept in the same order as the EContactField enum */
+static const EContactFieldInfo field_info[] = {
+	{0,}, /* Dummy row as EContactField starts from 1 */
+ 	STRING_FIELD (E_CONTACT_UID,        EVC_UID,       "id",         N_("Unique ID"),  FALSE),
+	STRING_FIELD (E_CONTACT_FILE_AS,    EVC_X_FILE_AS, "file-as",    N_("File Under"),    FALSE),
+	STRING_FIELD (E_CONTACT_BOOK_URI, EVC_X_BOOK_URI, "book-uri", N_("Book URI"), FALSE),
+
+	/* Name fields */
+	GETSET_FIELD        (E_CONTACT_FULL_NAME,   EVC_FN,       "full-name",   N_("Full Name"),   FALSE, fn_getter, fn_setter),
+	LIST_ELEM_STR_FIELD (E_CONTACT_GIVEN_NAME,  EVC_N,        "given-name",  N_("Given Name"),  FALSE, 1),
+	LIST_ELEM_STR_FIELD (E_CONTACT_FAMILY_NAME, EVC_N,        "family-name", N_("Family Name"), FALSE, 0),
+	STRING_FIELD        (E_CONTACT_NICKNAME,    EVC_NICKNAME, "nickname",    N_("Nickname"),    FALSE),
+
+	/* Email fields */
+	MULTI_ELEM_STR_FIELD (E_CONTACT_EMAIL_1,    EVC_EMAIL,        "email-1",    N_("Email 1"),         FALSE, 0),
+	MULTI_ELEM_STR_FIELD (E_CONTACT_EMAIL_2,    EVC_EMAIL,        "email-2",    N_("Email 2"),         FALSE, 1),
+	MULTI_ELEM_STR_FIELD (E_CONTACT_EMAIL_3,    EVC_EMAIL,        "email-3",    N_("Email 3"),         FALSE, 2),
+	MULTI_ELEM_STR_FIELD (E_CONTACT_EMAIL_4,    EVC_EMAIL,        "email-4",    N_("Email 4"),         FALSE, 3),
+
+	STRING_FIELD         (E_CONTACT_MAILER,     EVC_MAILER,       "mailer",     N_("Mailer"),          FALSE),
+
+	/* Address Labels */
+	ATTR_TYPE_STR_FIELD (E_CONTACT_ADDRESS_LABEL_HOME,  EVC_LABEL, "address-label-home",  N_("Home Address Label"),  FALSE, "HOME", 0),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_ADDRESS_LABEL_WORK,  EVC_LABEL, "address-label-work",  N_("Work Address Label"),  FALSE, "WORK", 0),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_ADDRESS_LABEL_OTHER, EVC_LABEL, "address-label-other", N_("Other Address Label"), FALSE, "OTHER", 0),
+
+	/* Phone fields */
+	ATTR_TYPE_STR_FIELD  (E_CONTACT_PHONE_ASSISTANT,    EVC_TEL, "assistant-phone",   N_("Assistant Phone"),  FALSE, EVC_X_ASSISTANT, 0),
+	ATTR2_TYPE_STR_FIELD (E_CONTACT_PHONE_BUSINESS,     EVC_TEL, "business-phone",    N_("Business Phone"),   FALSE, "WORK", "VOICE",         0),
+	ATTR2_TYPE_STR_FIELD (E_CONTACT_PHONE_BUSINESS_2,   EVC_TEL, "business-phone-2",  N_("Business Phone 2"), FALSE, "WORK", "VOICE",         1),
+	ATTR2_TYPE_STR_FIELD (E_CONTACT_PHONE_BUSINESS_FAX, EVC_TEL, "business-fax",      N_("Business Fax"),     FALSE, "WORK", "FAX",           0),
+	ATTR_TYPE_STR_FIELD  (E_CONTACT_PHONE_CALLBACK,     EVC_TEL, "callback-phone",    N_("Callback Phone"),   FALSE, EVC_X_CALLBACK,  0),
+	ATTR_TYPE_STR_FIELD  (E_CONTACT_PHONE_CAR,          EVC_TEL, "car-phone",         N_("Car Phone"),        FALSE, "CAR",                   0),
+	ATTR_TYPE_STR_FIELD  (E_CONTACT_PHONE_COMPANY,      EVC_TEL, "company-phone",     N_("Company Phone"),    FALSE, EVC_X_COMPANY,   0),
+	ATTR2_TYPE_STR_FIELD (E_CONTACT_PHONE_HOME,         EVC_TEL, "home-phone",        N_("Home Phone"),       FALSE, "HOME", "VOICE",         0),
+	ATTR2_TYPE_STR_FIELD (E_CONTACT_PHONE_HOME_2,       EVC_TEL, "home-phone-2",      N_("Home Phone 2"),     FALSE, "HOME", "VOICE",         1),
+	ATTR2_TYPE_STR_FIELD (E_CONTACT_PHONE_HOME_FAX,     EVC_TEL, "home-fax",          N_("Home Fax"),         FALSE, "HOME", "FAX",           0),
+	ATTR_TYPE_STR_FIELD  (E_CONTACT_PHONE_ISDN,         EVC_TEL, "isdn-phone",        N_("ISDN"),             FALSE, "ISDN",                  0),
+	ATTR_TYPE_STR_FIELD  (E_CONTACT_PHONE_MOBILE,       EVC_TEL, "mobile-phone",      N_("Mobile Phone"),     FALSE, "CELL",                  0),
+	ATTR_TYPE_STR_FIELD  (E_CONTACT_PHONE_OTHER,        EVC_TEL, "other-phone",       N_("Other Phone"),      FALSE, "VOICE",                 0),
+	ATTR_TYPE_STR_FIELD  (E_CONTACT_PHONE_OTHER_FAX,    EVC_TEL, "other-fax",         N_("Other Fax"),        FALSE, "FAX",                   0),
+	ATTR_TYPE_STR_FIELD  (E_CONTACT_PHONE_PAGER,        EVC_TEL, "pager",             N_("Pager"),            FALSE, "PAGER",                 0),
+	ATTR_TYPE_STR_FIELD  (E_CONTACT_PHONE_PRIMARY,      EVC_TEL, "primary-phone",     N_("Primary Phone"),    FALSE, "PREF",                  0),
+	ATTR_TYPE_STR_FIELD  (E_CONTACT_PHONE_RADIO,        EVC_TEL, "radio",             N_("Radio"),            FALSE, EVC_X_RADIO,     0),
+	ATTR_TYPE_STR_FIELD  (E_CONTACT_PHONE_TELEX,        EVC_TEL, "telex",             N_("Telex"),            FALSE, EVC_X_TELEX,     0),
+	ATTR_TYPE_STR_FIELD  (E_CONTACT_PHONE_TTYTDD,       EVC_TEL, "tty",               N_("TTY"),              FALSE, EVC_X_TTYTDD,    0),
+	
+	/* Organizational fields */
+	LIST_ELEM_STR_FIELD (E_CONTACT_ORG,      EVC_ORG, "org",      N_("Organization"),        FALSE, 0),
+	LIST_ELEM_STR_FIELD (E_CONTACT_ORG_UNIT, EVC_ORG, "org-unit", N_("Organizational Unit"), FALSE, 1),
+	LIST_ELEM_STR_FIELD (E_CONTACT_OFFICE,   EVC_ORG, "office",   N_("Office"),              FALSE, 2),
+	STRING_FIELD    (E_CONTACT_TITLE,     EVC_TITLE,       "title",     N_("Title"),           FALSE),
+	STRING_FIELD    (E_CONTACT_ROLE,      EVC_ROLE,        "role",      N_("Role"),            FALSE),
+	STRING_FIELD    (E_CONTACT_MANAGER,   EVC_X_MANAGER,   "manager",   N_("Manager"),         FALSE),
+	STRING_FIELD    (E_CONTACT_ASSISTANT, EVC_X_ASSISTANT, "assistant", N_("Assistant"),       FALSE),
+
+	/* Web fields */
+	STRING_FIELD (E_CONTACT_HOMEPAGE_URL, EVC_URL,         "homepage-url", N_("Homepage URL"), FALSE),
+	STRING_FIELD (E_CONTACT_BLOG_URL,     EVC_X_BLOG_URL,  "blog-url",     N_("Weblog URL"),   FALSE),
+
+	/* Contact categories */
+	SYNTH_STR_FIELD (E_CONTACT_CATEGORIES,                    "categories",    N_("Categories"),    FALSE),
+
+	/* Collaboration fields */
+	STRING_FIELD (E_CONTACT_CALENDAR_URI, EVC_CALURI,      "caluri",     N_("Calendar URI"),  FALSE),
+	STRING_FIELD (E_CONTACT_FREEBUSY_URL, EVC_FBURL,       "fburl",       N_("Free/Busy URL"), FALSE),
+	STRING_FIELD (E_CONTACT_ICS_CALENDAR, EVC_ICSCALENDAR, "icscalendar", N_("ICS Calendar"),  FALSE),
+	STRING_FIELD (E_CONTACT_VIDEO_URL,    EVC_X_VIDEO_URL, "video-url",    N_("Video Conferencing URL"),   FALSE),
+
+	/* Misc fields */
+	STRING_FIELD (E_CONTACT_SPOUSE, EVC_X_SPOUSE,    "spouse", N_("Spouse's Name"), FALSE),
+	STRING_FIELD (E_CONTACT_NOTE,   EVC_NOTE,        "note",   N_("Note"),          FALSE),
+
+	ATTR_TYPE_STR_FIELD (E_CONTACT_IM_AIM_HOME_1,    EVC_X_AIM,    "im-aim-home-1",    N_("AIM Home Screen Name 1"),    FALSE, "HOME", 0),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_IM_AIM_HOME_2,    EVC_X_AIM,    "im-aim-home-2",    N_("AIM Home Screen Name 2"),    FALSE, "HOME", 1),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_IM_AIM_HOME_3,    EVC_X_AIM,    "im-aim-home-3",    N_("AIM Home Screen Name 3"),    FALSE, "HOME", 2),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_IM_AIM_WORK_1,    EVC_X_AIM,    "im-aim-work-1",    N_("AIM Work Screen Name 1"),    FALSE, "WORK", 0),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_IM_AIM_WORK_2,    EVC_X_AIM,    "im-aim-work-2",    N_("AIM Work Screen Name 2"),    FALSE, "WORK", 1),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_IM_AIM_WORK_3,    EVC_X_AIM,    "im-aim-work-3",    N_("AIM Work Screen Name 3"),    FALSE, "WORK", 2),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_IM_GROUPWISE_HOME_1, EVC_X_GROUPWISE, "im-groupwise-home-1", N_("GroupWise Home Screen Name 1"),    FALSE, "HOME", 0),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_IM_GROUPWISE_HOME_2, EVC_X_GROUPWISE, "im-groupwise-home-2", N_("GroupWise Home Screen Name 2"),    FALSE, "HOME", 1),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_IM_GROUPWISE_HOME_3, EVC_X_GROUPWISE, "im-groupwise-home-3", N_("GroupWise Home Screen Name 3"),    FALSE, "HOME", 2),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_IM_GROUPWISE_WORK_1, EVC_X_GROUPWISE, "im-groupwise-work-1", N_("GroupWise Work Screen Name 1"),    FALSE, "WORK", 0),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_IM_GROUPWISE_WORK_2, EVC_X_GROUPWISE, "im-groupwise-work-2", N_("GroupWise Work Screen Name 2"),    FALSE, "WORK", 1),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_IM_GROUPWISE_WORK_3, EVC_X_GROUPWISE, "im-groupwise-work-3", N_("GroupWise Work Screen Name 3"),    FALSE, "WORK", 2),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_IM_JABBER_HOME_1, EVC_X_JABBER, "im-jabber-home-1", N_("Jabber Home Id 1"),          FALSE, "HOME", 0),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_IM_JABBER_HOME_2, EVC_X_JABBER, "im-jabber-home-2", N_("Jabber Home Id 2"),          FALSE, "HOME", 1),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_IM_JABBER_HOME_3, EVC_X_JABBER, "im-jabber-home-3", N_("Jabber Home Id 3"),          FALSE, "HOME", 2),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_IM_JABBER_WORK_1, EVC_X_JABBER, "im-jabber-work-1", N_("Jabber Work Id 1"),          FALSE, "WORK", 0),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_IM_JABBER_WORK_2, EVC_X_JABBER, "im-jabber-work-3", N_("Jabber Work Id 2"),          FALSE, "WORK", 1),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_IM_JABBER_WORK_3, EVC_X_JABBER, "im-jabber-work-2", N_("Jabber Work Id 3"),          FALSE, "WORK", 2),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_IM_YAHOO_HOME_1,  EVC_X_YAHOO,  "im-yahoo-home-1",  N_("Yahoo! Home Screen Name 1"), FALSE, "HOME", 0),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_IM_YAHOO_HOME_2,  EVC_X_YAHOO,  "im-yahoo-home-2",  N_("Yahoo! Home Screen Name 2"), FALSE, "HOME", 1),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_IM_YAHOO_HOME_3,  EVC_X_YAHOO,  "im-yahoo-home-3",  N_("Yahoo! Home Screen Name 3"), FALSE, "HOME", 2),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_IM_YAHOO_WORK_1,  EVC_X_YAHOO,  "im-yahoo-work-1",  N_("Yahoo! Work Screen Name 1"), FALSE, "WORK", 0),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_IM_YAHOO_WORK_2,  EVC_X_YAHOO,  "im-yahoo-work-2",  N_("Yahoo! Work Screen Name 2"), FALSE, "WORK", 1),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_IM_YAHOO_WORK_3,  EVC_X_YAHOO,  "im-yahoo-work-3",  N_("Yahoo! Work Screen Name 3"), FALSE, "WORK", 2),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_IM_MSN_HOME_1,    EVC_X_MSN,    "im-msn-home-1",    N_("MSN Home Screen Name 1"),    FALSE, "HOME", 0),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_IM_MSN_HOME_2,    EVC_X_MSN,    "im-msn-home-2",    N_("MSN Home Screen Name 2"),    FALSE, "HOME", 1),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_IM_MSN_HOME_3,    EVC_X_MSN,    "im-msn-home-3",    N_("MSN Home Screen Name 3"),    FALSE, "HOME", 2),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_IM_MSN_WORK_1,    EVC_X_MSN,    "im-msn-work-1",    N_("MSN Work Screen Name 1"),    FALSE, "WORK", 0),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_IM_MSN_WORK_2,    EVC_X_MSN,    "im-msn-work-2",    N_("MSN Work Screen Name 2"),    FALSE, "WORK", 1),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_IM_MSN_WORK_3,    EVC_X_MSN,    "im-msn-work-3",    N_("MSN Work Screen Name 3"),    FALSE, "WORK", 2),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_IM_ICQ_HOME_1,    EVC_X_ICQ,    "im-icq-home-1",    N_("ICQ Home Id 1"),             FALSE, "HOME", 0),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_IM_ICQ_HOME_2,    EVC_X_ICQ,    "im-icq-home-2",    N_("ICQ Home Id 2"),             FALSE, "HOME", 1),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_IM_ICQ_HOME_3,    EVC_X_ICQ,    "im-icq-home-3",    N_("ICQ Home Id 3"),             FALSE, "HOME", 2),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_IM_ICQ_WORK_1,    EVC_X_ICQ,    "im-icq-work-1",    N_("ICQ Work Id 1"),             FALSE, "WORK", 0),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_IM_ICQ_WORK_2,    EVC_X_ICQ,    "im-icq-work-2",    N_("ICQ Work Id 2"),             FALSE, "WORK", 1),
+	ATTR_TYPE_STR_FIELD (E_CONTACT_IM_ICQ_WORK_3,    EVC_X_ICQ,    "im-icq-work-3",    N_("ICQ Work Id 3"),             FALSE, "WORK", 2),
+
+	/* Last modified time */
+	STRING_FIELD (E_CONTACT_REV, EVC_REV, "Rev", N_("Last Revision"), FALSE),
+	SYNTH_STR_FIELD     (E_CONTACT_NAME_OR_ORG,               "name-or-org", N_("Name or Org"), TRUE),
+
+	/* Address fields */
+	MULTI_LIST_FIELD       (E_CONTACT_ADDRESS,       EVC_ADR, "address",       N_("Address List"),  FALSE),
+	ATTR_TYPE_STRUCT_FIELD (E_CONTACT_ADDRESS_HOME,  EVC_ADR, "address-home",  N_("Home Address"),  FALSE, "HOME",  adr_getter, adr_setter, e_contact_address_get_type),
+	ATTR_TYPE_STRUCT_FIELD (E_CONTACT_ADDRESS_WORK,  EVC_ADR, "address-work",  N_("Work Address"),  FALSE, "WORK",  adr_getter, adr_setter, e_contact_address_get_type),
+	ATTR_TYPE_STRUCT_FIELD (E_CONTACT_ADDRESS_OTHER, EVC_ADR, "address-other", N_("Other Address"), FALSE, "OTHER", adr_getter, adr_setter, e_contact_address_get_type),
+
+	/* Contact categories */
+	LIST_FIELD      (E_CONTACT_CATEGORY_LIST, EVC_CATEGORIES, "category-list", N_("Category List"), FALSE),
+
+	/* Photo/Logo */
+	STRUCT_FIELD    (E_CONTACT_PHOTO, EVC_PHOTO, "photo", N_("Photo"), FALSE, photo_getter, photo_setter, e_contact_photo_get_type),
+	STRUCT_FIELD    (E_CONTACT_LOGO,  EVC_LOGO,  "logo",  N_("Logo"),  FALSE, photo_getter, photo_setter, e_contact_photo_get_type),
+
+	STRUCT_FIELD        (E_CONTACT_NAME,        EVC_N,        "name",        N_("Name"),        FALSE, n_getter, n_setter, e_contact_name_get_type),
+	MULTI_LIST_FIELD     (E_CONTACT_EMAIL,      EVC_EMAIL,        "email",      N_("Email List"),      FALSE),
+
+	/* Instant messaging fields */
+	MULTI_LIST_FIELD (E_CONTACT_IM_AIM,       EVC_X_AIM,       "im-aim",       N_("AIM Screen Name List"),    FALSE),
+	MULTI_LIST_FIELD (E_CONTACT_IM_GROUPWISE, EVC_X_GROUPWISE, "im-groupwise", N_("GroupWise Id List"),       FALSE),
+	MULTI_LIST_FIELD (E_CONTACT_IM_JABBER, 	  EVC_X_JABBER,    "im-jabber",    N_("Jabber Id List"),          FALSE),
+	MULTI_LIST_FIELD (E_CONTACT_IM_YAHOO,  	  EVC_X_YAHOO,     "im-yahoo",     N_("Yahoo! Screen Name List"), FALSE),
+	MULTI_LIST_FIELD (E_CONTACT_IM_MSN,    	  EVC_X_MSN,       "im-msn",       N_("MSN Screen Name List"),    FALSE),
+	MULTI_LIST_FIELD (E_CONTACT_IM_ICQ,    	  EVC_X_ICQ,       "im-icq",       N_("ICQ Id List"),             FALSE),
+
+	BOOLEAN_FIELD        (E_CONTACT_WANTS_HTML, EVC_X_WANTS_HTML, "wants-html", N_("Wants HTML Mail"), FALSE),
+
+	BOOLEAN_FIELD (E_CONTACT_IS_LIST,             EVC_X_LIST, "list", N_("List"), FALSE),
+	BOOLEAN_FIELD (E_CONTACT_LIST_SHOW_ADDRESSES, EVC_X_LIST_SHOW_ADDRESSES, "list-show-addresses", N_("List Show Addresses"), FALSE),
+
+	STRUCT_FIELD (E_CONTACT_BIRTH_DATE,  EVC_BDAY,          "birth-date",  N_("Birth Date"), FALSE, date_getter, date_setter, e_contact_date_get_type),
+	STRUCT_FIELD (E_CONTACT_ANNIVERSARY, EVC_X_ANNIVERSARY, "anniversary", N_("Anniversary"), FALSE, date_getter, date_setter, e_contact_date_get_type),
+
+	/* Security fields */
+	ATTR_TYPE_STRUCT_FIELD (E_CONTACT_X509_CERT,  EVC_KEY, "x509Cert",  N_("X.509 Certificate"), FALSE, "X509", cert_getter, cert_setter, e_contact_cert_get_type), 
+
+	MULTI_LIST_FIELD (E_CONTACT_OSSO_CONTACT_STATE,       EVC_X_OSSO_CONTACT_STATE,       "osso-contact-state",       N_("Contact State"),    FALSE),
+};
+
+#undef LIST_ELEM_STR_FIELD
+#undef STRING_FIELD
+#undef SYNTH_STR_FIELD
+#undef LIST_FIELD
+#undef GETSET_FIELD
+
+static GObjectClass *parent_class;
+
+static void e_contact_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
+static void e_contact_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
+
+static void
+e_contact_dispose (GObject *object)
+{
+	EContact *ec = E_CONTACT (object);
+
+	if (ec->priv) {
+		g_free (ec->priv);
+		ec->priv = NULL;
+	}
+
+	if (G_OBJECT_CLASS (parent_class)->dispose)
+		G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+e_contact_class_init (EContactClass *klass)
+{
+	GObjectClass *object_class;
+	int i;
+
+	object_class = G_OBJECT_CLASS(klass);
+
+	parent_class = g_type_class_ref (E_TYPE_VCARD);
+
+	object_class->dispose = e_contact_dispose;
+	object_class->set_property = e_contact_set_property;
+	object_class->get_property = e_contact_get_property;
+
+	for (i = E_CONTACT_FIELD_FIRST; i < E_CONTACT_FIELD_LAST; i++) {
+		GParamSpec *pspec = NULL;
+
+		/* Verify the table is correctly ordered */
+		g_assert (i == field_info[i].field_id);
+		
+		if (field_info[i].t & E_CONTACT_FIELD_TYPE_STRING)
+			pspec = g_param_spec_string (field_info[i].field_name,
+						     _(field_info[i].pretty_name),
+						     "" /* XXX blurb */,
+						     NULL,
+						     field_info[i].read_only ? E_PARAM_READABLE : E_PARAM_READWRITE);
+		else if (field_info[i].t & E_CONTACT_FIELD_TYPE_BOOLEAN)
+			pspec = g_param_spec_boolean (field_info[i].field_name,
+						      _(field_info[i].pretty_name),
+						      "" /* XXX blurb */,
+						      FALSE,
+						      field_info[i].read_only ? E_PARAM_READABLE : E_PARAM_READWRITE);
+		else if (field_info[i].t & E_CONTACT_FIELD_TYPE_STRUCT)
+			pspec = g_param_spec_boxed (field_info[i].field_name,
+						    _(field_info[i].pretty_name),
+						    "" /* XXX blurb */,
+						    field_info[i].boxed_type_getter(),
+						    field_info[i].read_only ? E_PARAM_READABLE : E_PARAM_READWRITE);
+		else
+			pspec = g_param_spec_pointer (field_info[i].field_name,
+						      _(field_info[i].pretty_name),
+						      "" /* XXX blurb */,
+						      field_info[i].read_only ? E_PARAM_READABLE : E_PARAM_READWRITE);
+
+		g_object_class_install_property (object_class, field_info[i].field_id,
+						 pspec);
+	}
+}
+
+static void
+e_contact_init (EContact *ec)
+{
+	ec->priv = g_new0 (EContactPrivate, 1);
+}
+
+GType
+e_contact_get_type (void)
+{
+	static GType contact_type = 0;
+
+	if (!contact_type) {
+		static const GTypeInfo contact_info =  {
+			sizeof (EContactClass),
+			NULL,           /* base_init */
+			NULL,           /* base_finalize */
+			(GClassInitFunc) e_contact_class_init,
+			NULL,           /* class_finalize */
+			NULL,           /* class_data */
+			sizeof (EContact),
+			0,             /* n_preallocs */
+			(GInstanceInitFunc) e_contact_init,
+		};
+
+		contact_type = g_type_register_static (E_TYPE_VCARD, "EContact", &contact_info, 0);
+	}
+
+	return contact_type;
+}
+
+static EVCardAttribute*
+e_contact_get_first_attr (EContact *contact, const char *attr_name)
+{
+	GList *attrs, *l;
+
+	attrs = e_vcard_get_attributes (E_VCARD (contact));
+
+	for (l = attrs; l; l = l->next) {
+		EVCardAttribute *attr = l->data;
+		const char *name;
+
+		name = e_vcard_attribute_get_name (attr);
+
+		if (!strcmp (name, attr_name))
+			return attr;
+	}
+
+	return NULL;
+}
+
+
+
+static void*
+photo_getter (EContact *contact, EVCardAttribute *attr)
+{
+	GList *values;
+
+	if (!attr)
+		return NULL;
+	
+	values = e_vcard_attribute_get_param (attr, EVC_ENCODING);
+	if (values && g_ascii_strcasecmp (values->data, "b") == 0) {
+		values = e_vcard_attribute_get_values_decoded (attr);	
+		if (values && values->data) {
+			GString *s = values->data;
+			EContactPhoto *photo;
+			
+			if (!s->len)
+				return NULL;
+			
+			photo = g_new0 (EContactPhoto, 1);
+			photo->type = E_CONTACT_PHOTO_TYPE_INLINED;
+			photo->data.inlined.length = s->len;
+			photo->data.inlined.data = g_malloc (photo->data.inlined.length);
+			memcpy (photo->data.inlined.data, s->str, photo->data.inlined.length);
+
+			values = e_vcard_attribute_get_param (attr, EVC_TYPE);
+			if (values && values->data)
+				photo->data.inlined.mime_type = g_strdup_printf("image/%s", (char*)values->data);
+			return photo;
+		}
+	}
+
+	values = e_vcard_attribute_get_param (attr, EVC_VALUE);
+	if (values && g_ascii_strcasecmp (values->data, "uri") == 0) {
+		EContactPhoto *photo;
+		photo = g_new0 (EContactPhoto, 1);
+		photo->type = E_CONTACT_PHOTO_TYPE_URI;
+		photo->data.uri = e_vcard_attribute_get_value (attr);
+		return photo;
+	}
+	return NULL;
+}
+
+static void
+photo_setter (EContact *contact, EVCardAttribute *attr, void *data)
+{
+	EContactPhoto *photo = data;
+	char *image_type, *p;
+
+	switch (photo->type) {
+	case E_CONTACT_PHOTO_TYPE_INLINED:
+		g_return_if_fail (photo->data.inlined.length > 0);
+		
+		e_vcard_attribute_add_param_with_value (attr,
+							e_vcard_attribute_param_new (EVC_ENCODING),
+							"b");
+		if (photo->data.inlined.mime_type && (p = strchr (photo->data.inlined.mime_type, '/'))) {
+			image_type = p+1;
+		} else {
+			image_type = "X-EVOLUTION-UNKNOWN";
+		}
+		e_vcard_attribute_add_param_with_value (attr,
+							e_vcard_attribute_param_new (EVC_TYPE),
+							image_type);
+		
+		e_vcard_attribute_add_value_decoded (attr, (char*)photo->data.inlined.data, photo->data.inlined.length);
+		break;
+	case E_CONTACT_PHOTO_TYPE_URI:
+		e_vcard_attribute_add_param_with_value (attr,
+							e_vcard_attribute_param_new (EVC_VALUE),
+							"uri");
+		e_vcard_attribute_add_value (attr, photo->data.uri);
+		break;
+	default:
+		g_warning ("Unknown EContactPhotoType %d", photo->type);
+		break;
+	}
+}
+
+
+static void*
+fn_getter (EContact *contact, EVCardAttribute *attr)
+{
+	if (attr) {
+		GList *p = e_vcard_attribute_get_values (attr);
+
+		return p && p->data ? p->data : "";
+	}
+	else
+		return NULL;
+}
+
+static void
+fn_setter (EContact *contact, EVCardAttribute *attr, void *data)
+{
+	gchar *name_str = data;
+
+	e_vcard_attribute_add_value (attr, name_str);
+
+	attr = e_contact_get_first_attr (contact, EVC_N);
+	if (!attr) {
+		EContactName *name = e_contact_name_from_string ((char*)data);
+
+		attr = e_vcard_attribute_new (NULL, EVC_N);
+		e_vcard_add_attribute (E_VCARD (contact), attr);
+
+		/* call the setter directly */
+		n_setter (contact, attr, name);
+
+		e_contact_name_free (name);
+	}
+}
+
+
+
+static void*
+n_getter (EContact *contact, EVCardAttribute *attr)
+{
+	EContactName *name = g_new0 (EContactName, 1);
+	EVCardAttribute *new_attr;
+	char *name_str;
+
+	if (attr) {
+		GList *p = e_vcard_attribute_get_values (attr);
+
+		name->family     = g_strdup (p && p->data ? p->data : ""); if (p) p = p->next;
+		name->given      = g_strdup (p && p->data ? p->data : ""); if (p) p = p->next;
+		name->additional = g_strdup (p && p->data ? p->data : ""); if (p) p = p->next;
+		name->prefixes   = g_strdup (p && p->data ? p->data : ""); if (p) p = p->next;
+		name->suffixes   = g_strdup (p && p->data ? p->data : "");
+	}
+
+	new_attr = e_contact_get_first_attr (contact, EVC_FN);
+	if (!new_attr) {
+		new_attr = e_vcard_attribute_new (NULL, EVC_FN);
+		e_vcard_add_attribute (E_VCARD (contact), new_attr);
+		name_str = e_contact_name_to_string (name);
+		e_vcard_attribute_add_value (new_attr, name_str);
+		g_free (name_str);
+	}
+
+	return name;
+}
+
+static void
+n_setter (EContact *contact, EVCardAttribute *attr, void *data)
+{
+	EContactName *name = data;
+
+	e_vcard_attribute_add_value (attr, name->family);
+	e_vcard_attribute_add_value (attr, name->given);
+	e_vcard_attribute_add_value (attr, name->additional);
+	e_vcard_attribute_add_value (attr, name->prefixes);
+	e_vcard_attribute_add_value (attr, name->suffixes);
+
+	/* now find the attribute for FileAs.  if it's not present, fill it in */
+	attr = e_contact_get_first_attr (contact, EVC_X_FILE_AS);
+	if (!attr) {
+		char *strings[3], **stringptr;
+		char *string;
+		attr = e_vcard_attribute_new (NULL, EVC_X_FILE_AS);
+		e_vcard_add_attribute (E_VCARD (contact), attr);
+
+		stringptr = strings;
+		if (name->family && *name->family)
+			*(stringptr++) = name->family;
+		if (name->given && *name->given)
+			*(stringptr++) = name->given;
+		*stringptr = NULL;
+		string = g_strjoinv(", ", strings);
+
+		e_vcard_attribute_add_value (attr, string);
+		g_free (string);
+	}
+
+}
+
+
+
+static void*
+adr_getter (EContact *contact, EVCardAttribute *attr)
+{
+	if (attr) {
+		GList *p = e_vcard_attribute_get_values (attr);
+		EContactAddress *addr = g_new0 (EContactAddress, 1);
+
+		addr->address_format = g_strdup ("");
+		addr->po       = g_strdup (p && p->data ? p->data : ""); if (p) p = p->next;
+		addr->ext      = g_strdup (p && p->data ? p->data : ""); if (p) p = p->next;
+		addr->street   = g_strdup (p && p->data ? p->data : ""); if (p) p = p->next;
+		addr->locality = g_strdup (p && p->data ? p->data : ""); if (p) p = p->next;
+		addr->region   = g_strdup (p && p->data ? p->data : ""); if (p) p = p->next;
+		addr->code     = g_strdup (p && p->data ? p->data : ""); if (p) p = p->next;
+		addr->country  = g_strdup (p && p->data ? p->data : ""); if (p) p = p->next;
+
+		return addr;
+	}
+
+	return NULL;
+}
+
+static void
+adr_setter (EContact *contact, EVCardAttribute *attr, void *data)
+{
+	EContactAddress *addr = data;
+
+	e_vcard_attribute_add_value (attr, addr->po);
+	e_vcard_attribute_add_value (attr, addr->ext);
+	e_vcard_attribute_add_value (attr, addr->street);
+	e_vcard_attribute_add_value (attr, addr->locality);
+	e_vcard_attribute_add_value (attr, addr->region);
+	e_vcard_attribute_add_value (attr, addr->code);
+	e_vcard_attribute_add_value (attr, addr->country);
+}
+
+
+
+static void*
+date_getter (EContact *contact, EVCardAttribute *attr)
+{
+	if (attr) {
+		GList *p = e_vcard_attribute_get_values (attr);
+		EContactDate *date;
+
+		if (p && p->data && ((gchar *) p->data) [0])
+			date = e_contact_date_from_string ((char *) p->data);
+		else
+			date = NULL;
+
+		return date;
+	}
+
+	return NULL;
+}
+
+static void
+date_setter (EContact *contact, EVCardAttribute *attr, void *data)
+{
+	EContactDate *date = data;
+	char *str;
+
+	str = e_contact_date_to_string (date);
+
+	e_vcard_attribute_add_value (attr, str);
+	g_free (str);
+}
+
+
+
+static void*
+cert_getter (EContact *contact, EVCardAttribute *attr)
+{
+	if (attr) {
+		/* the certificate is stored in this vcard.  just
+		   return the data */
+		GList *values = e_vcard_attribute_get_values_decoded (attr);
+
+		if (values && values->data) {
+			GString *s = values->data;
+			EContactCert *cert = g_new0 (EContactCert, 1);
+
+			cert->length = s->len;
+			cert->data = g_malloc (cert->length);
+			memcpy (cert->data, s->str, cert->length);
+
+			return cert;
+		}
+	}
+
+	/* XXX if we stored a fingerprint in the cert we could look it
+	   up via NSS, but that would require the additional NSS dep
+	   here, and we'd have more than one process opening the
+	   certdb, which is bad.  *sigh* */
+	
+	return NULL;
+}
+
+static void
+cert_setter (EContact *contact, EVCardAttribute *attr, void *data)
+{
+	EContactCert *cert = data;
+
+	e_vcard_attribute_add_param_with_value (attr,
+						e_vcard_attribute_param_new (EVC_ENCODING),
+						"b");
+
+	e_vcard_attribute_add_value_decoded (attr, cert->data, cert->length);
+}
+
+
+
+/* Set_arg handler for the contact */
+static void
+e_contact_set_property (GObject *object,
+			guint prop_id,
+			const GValue *value,
+			GParamSpec *pspec)
+{
+	EContact *contact = E_CONTACT (object);
+	const EContactFieldInfo *info = NULL;
+
+	if (prop_id < 1 || prop_id >= E_CONTACT_FIELD_LAST) {
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		return;
+	}
+
+	info = &field_info[prop_id];
+
+	if (info->t & E_CONTACT_FIELD_TYPE_MULTI) {
+		GList *new_values = g_value_get_pointer (value);
+		GList *l;
+
+		/* first we remove all attributes of the type we're
+		   adding, then add new ones based on the values that
+		   are passed in */
+		e_vcard_remove_attributes (E_VCARD (contact), NULL, info->vcard_field_name);
+
+		for (l = new_values; l; l = l->next)
+			e_vcard_add_attribute_with_value (E_VCARD (contact),
+							  e_vcard_attribute_new (NULL, info->vcard_field_name),
+							  (char*)l->data);
+	}
+	else if (info->t & E_CONTACT_FIELD_TYPE_SYNTHETIC) {
+		if (info->t & E_CONTACT_FIELD_TYPE_MULTI_ELEM) {
+			/* XXX this is kinda broken - we don't insert
+			   insert padding elements if, e.g. the user
+			   sets email 3 when email 1 and 2 don't
+			   exist.  But, if we *did* pad the lists we'd
+			   end up with empty items in the vcard.  I
+			   dunno which is worse. */
+			EVCardAttribute *attr = NULL;
+			gboolean found = FALSE;
+			int num_left = info->list_elem;
+			GList *attrs = e_vcard_get_attributes (E_VCARD (contact));
+			GList *l;
+			const char *sval;
+
+			for (l = attrs; l; l = l->next) {
+				const char *name;
+
+				attr = l->data;
+				name = e_vcard_attribute_get_name (attr);
+
+				if (!strcmp (name, info->vcard_field_name)) {
+					if (num_left-- == 0) {
+						found = TRUE;
+						break;
+					}
+				}
+			}
+
+			sval = g_value_get_string (value);
+			if (sval && *sval) {
+				if (found) {
+					/* we found it, overwrite it */
+					e_vcard_attribute_remove_values (attr);
+				}
+				else {
+					/* we didn't find it - add a new attribute */
+					attr = e_vcard_attribute_new (NULL, info->vcard_field_name);
+					if (!strcmp(info->vcard_field_name, "EMAIL") && 
+					    !info->attr_type1 && 
+					    !info->attr_type2) {
+						/* Add default type */
+						e_vcard_attribute_add_param_with_value ( attr,
+								e_vcard_attribute_param_new (EVC_TYPE),
+								"OTHER");
+					}
+					e_vcard_add_attribute (E_VCARD (contact), attr);
+				}
+
+				e_vcard_attribute_add_value (attr, sval);
+			}
+			else {
+				if (found)
+					e_vcard_remove_attribute (E_VCARD (contact), attr);
+			}
+		}
+		else if (info->t & E_CONTACT_FIELD_TYPE_ATTR_TYPE) {
+			/* XXX this is kinda broken - we don't insert
+			   insert padding elements if, e.g. the user
+			   sets email 3 when email 1 and 2 don't
+			   exist.  But, if we *did* pad the lists we'd
+			   end up with empty items in the vcard.  I
+			   dunno which is worse. */
+			EVCardAttribute *attr = NULL;
+			gboolean found = FALSE;
+			int num_left = info->list_elem;
+			GList *attrs = e_vcard_get_attributes (E_VCARD (contact));
+			GList *l;
+
+			for (l = attrs; l && !found; l = l->next) {
+				const char *name;
+				gboolean found_needed1, found_needed2;
+
+				found_needed1 = (info->attr_type1 == NULL);
+				found_needed2 = (info->attr_type2 == NULL);
+
+				attr = l->data;
+				name = e_vcard_attribute_get_name (attr);
+
+				if (!strcmp (name, info->vcard_field_name)) {
+					GList *params;
+
+					for (params = e_vcard_attribute_get_params (attr); params; params = params->next) {
+						EVCardAttributeParam *param = params->data;
+						const char *name = e_vcard_attribute_param_get_name (param);
+
+						if (!strcmp (name, EVC_TYPE)) {
+							GList *values = e_vcard_attribute_param_get_values (param);
+							if (values && values->data) {
+								gboolean matches = FALSE;
+								if (!found_needed1 && !strcasecmp ((char*)values->data, info->attr_type1)) {
+									found_needed1 = TRUE;
+									matches = TRUE;
+								}
+								else if (!found_needed2 && !strcasecmp ((char*)values->data, info->attr_type2)) {
+									found_needed2 = TRUE;
+									matches = TRUE;
+								}
+								if (!matches) {
+									/* this is to enforce that we find an attribute
+									   with *only* the TYPE='s we need.  This may seem like
+									   an odd restriction but it's the only way at present to
+									   implement the Other Fax and Other Phone attributes. */
+									found_needed1 =
+										found_needed2 = FALSE;
+									break;
+								}
+							}
+						}
+
+						if (found_needed1 && found_needed2) {
+							if (num_left-- == 0) {
+								found = TRUE;
+								break;
+							}
+						}
+					}
+				}
+			}
+
+			if (found) {
+				/* we found it, overwrite it */
+				e_vcard_attribute_remove_values (attr);
+			}
+			else {
+				/* we didn't find it - add a new attribute */
+				attr = e_vcard_attribute_new (NULL, info->vcard_field_name);
+				e_vcard_add_attribute (E_VCARD (contact), attr);
+				if (info->attr_type1)
+					e_vcard_attribute_add_param_with_value (attr, e_vcard_attribute_param_new (EVC_TYPE),
+										info->attr_type1);
+				if (info->attr_type2)
+					e_vcard_attribute_add_param_with_value (attr, e_vcard_attribute_param_new (EVC_TYPE),
+										info->attr_type2);
+			}
+
+			if (info->t & E_CONTACT_FIELD_TYPE_STRUCT || info->t & E_CONTACT_FIELD_TYPE_GETSET) {
+				void *data = info->t & E_CONTACT_FIELD_TYPE_STRUCT ? g_value_get_boxed (value) : (char*)g_value_get_string (value);
+
+				if ((info->t & E_CONTACT_FIELD_TYPE_STRUCT && data)
+				    || (data && *(char*)data))
+					info->struct_setter (contact, attr, data);
+				else
+					e_vcard_remove_attribute (E_VCARD (contact), attr);
+			}
+			else {
+				const char *sval = g_value_get_string (value);
+
+				if (sval && *sval)
+					e_vcard_attribute_add_value (attr, sval);
+				else
+					e_vcard_remove_attribute (E_VCARD (contact), attr);
+			}
+		}
+		else if (info->t & E_CONTACT_FIELD_TYPE_LIST_ELEM) {
+			EVCardAttribute *attr = e_contact_get_first_attr (contact, info->vcard_field_name);
+			GList *values;
+			GList *p;
+			const char *sval = g_value_get_string (value);
+
+			if (!attr) {
+				if (!sval || !*sval)
+					return;
+
+				d(printf ("adding new %s\n", info->vcard_field_name));
+
+				attr = e_vcard_attribute_new (NULL, info->vcard_field_name);
+				e_vcard_add_attribute (E_VCARD (contact), attr);
+			}
+			
+			values = e_vcard_attribute_get_values (attr);
+			p = g_list_nth (values, info->list_elem);
+
+			if (p) {
+				g_free (p->data);
+				p->data = g_strdup (g_value_get_string (value));
+			}
+			else {
+				/* there weren't enough elements in the list, pad it */
+				int count = info->list_elem - g_list_length (values);
+
+				while (count--)
+					e_vcard_attribute_add_value (attr, "");
+
+				e_vcard_attribute_add_value (attr, g_value_get_string (value));
+			}
+		}
+		else {
+			switch (info->field_id) {
+			case E_CONTACT_CATEGORIES: {
+				EVCardAttribute *attr = e_contact_get_first_attr (contact, EVC_CATEGORIES);
+				char **split, **s;
+				const char *str;
+
+				if (attr)
+					e_vcard_attribute_remove_values (attr);
+				else {
+					/* we didn't find it - add a new attribute */
+					attr = e_vcard_attribute_new (NULL, EVC_CATEGORIES);
+					e_vcard_add_attribute (E_VCARD (contact), attr);
+				}
+
+				str = g_value_get_string (value);
+				if (str && *str) {
+					split = g_strsplit (str, ",", 0);
+					if (split) {
+						for (s = split; *s; s++) {
+							e_vcard_attribute_add_value (attr, g_strstrip (*s));
+						}
+						g_strfreev (split);
+					} else
+						e_vcard_attribute_add_value (attr, str);
+				}
+				else {
+					d(printf ("removing %s\n", info->vcard_field_name));
+
+					e_vcard_remove_attribute (E_VCARD (contact), attr);
+				}
+				break;
+			}
+			default:
+				g_warning ("unhandled synthetic field 0x%02x", info->field_id);
+				break;
+			}
+		}
+	}
+	else if (info->t & E_CONTACT_FIELD_TYPE_STRUCT || info->t & E_CONTACT_FIELD_TYPE_GETSET) {
+		EVCardAttribute *attr = e_contact_get_first_attr (contact, info->vcard_field_name);
+		void *data = info->t & E_CONTACT_FIELD_TYPE_STRUCT ? g_value_get_boxed (value) : (char*)g_value_get_string (value);
+
+		if (attr) {
+			if ((info->t & E_CONTACT_FIELD_TYPE_STRUCT && data)
+			    || (data && *(char*)data)) {
+				d(printf ("overwriting existing %s\n", info->vcard_field_name));
+				/* remove all existing values and parameters.
+				   the setter will add the correct ones */
+				e_vcard_attribute_remove_values (attr);
+				e_vcard_attribute_remove_params (attr);
+
+				info->struct_setter (contact, attr, data);
+			}
+			else {
+				d(printf ("removing %s\n", info->vcard_field_name));
+
+				e_vcard_remove_attribute (E_VCARD (contact), attr);
+			}
+		}
+		else if ((info->t & E_CONTACT_FIELD_TYPE_STRUCT && data)
+			 || (data && *(char*)data)) {
+			d(printf ("adding new %s\n", info->vcard_field_name));
+			attr = e_vcard_attribute_new (NULL, info->vcard_field_name);
+
+			e_vcard_add_attribute (E_VCARD (contact), attr);
+
+			info->struct_setter (contact, attr, data);
+		}
+	}
+	else if (info->t & E_CONTACT_FIELD_TYPE_BOOLEAN) {
+		EVCardAttribute *attr;
+
+		/* first we search for an attribute we can overwrite */
+		attr = e_contact_get_first_attr (contact, info->vcard_field_name);
+		if (attr) {
+			d(printf ("setting %s to `%s'\n", info->vcard_field_name, g_value_get_string (value)));
+			e_vcard_attribute_remove_values (attr);
+			e_vcard_attribute_add_value (attr, g_value_get_boolean (value) ? "TRUE" : "FALSE");
+		}
+		else {
+			/* and if we don't find one we create a new attribute */
+			e_vcard_add_attribute_with_value (E_VCARD (contact),
+							  e_vcard_attribute_new (NULL, info->vcard_field_name),
+							  g_value_get_boolean (value) ? "TRUE" : "FALSE");
+		}
+	}
+	else if (info->t & E_CONTACT_FIELD_TYPE_STRING) {
+		EVCardAttribute *attr;
+		const char *sval = g_value_get_string (value);
+
+		/* first we search for an attribute we can overwrite */
+		attr = e_contact_get_first_attr (contact, info->vcard_field_name);
+		if (attr) {
+			d(printf ("setting %s to `%s'\n", info->vcard_field_name, sval));
+			e_vcard_attribute_remove_values (attr);
+			if (sval) {
+				e_vcard_attribute_add_value (attr, sval);
+			}
+			else {
+				d(printf ("removing %s\n", info->vcard_field_name));
+
+				e_vcard_remove_attribute (E_VCARD (contact), attr);
+			}
+
+		}
+		else if (sval) {
+			/* and if we don't find one we create a new attribute */
+			e_vcard_add_attribute_with_value (E_VCARD (contact),
+							  e_vcard_attribute_new (NULL, info->vcard_field_name),
+							  g_value_get_string (value));
+		}
+	}
+	else if (info->t & E_CONTACT_FIELD_TYPE_LIST) {
+		EVCardAttribute *attr;
+		GList *values, *l;
+
+		values = g_value_get_pointer (value);
+
+		attr = e_contact_get_first_attr (contact, info->vcard_field_name);
+
+		if (attr) {
+			e_vcard_attribute_remove_values (attr);
+
+			if (!values)
+				e_vcard_remove_attribute (E_VCARD (contact), attr);
+		}
+		else if (values) {
+			attr = e_vcard_attribute_new (NULL, info->vcard_field_name);
+			e_vcard_add_attribute (E_VCARD (contact), attr);
+		}
+
+		for (l = values; l != NULL; l = l->next)
+			e_vcard_attribute_add_value (attr, l->data);
+	}
+	else {
+		g_warning ("unhandled attribute `%s'", info->vcard_field_name);
+	}
+}
+
+static EVCardAttribute *
+e_contact_find_attribute_with_types (EContact *contact, const char *attr_name, const char *type_needed1, const char *type_needed2, int nth)
+{
+	GList *l, *attrs;
+	gboolean found_needed1, found_needed2;
+
+	attrs = e_vcard_get_attributes (E_VCARD (contact));
+
+	for (l = attrs; l; l = l->next) {
+		EVCardAttribute *attr = l->data;
+		const char *name;
+
+		found_needed1 = (type_needed1 == NULL);
+		found_needed2 = (type_needed2 == NULL);
+
+		name = e_vcard_attribute_get_name (attr);
+
+		if (!strcmp (name, attr_name)) {
+			GList *params;
+
+			for (params = e_vcard_attribute_get_params (attr); params; params = params->next) {
+				EVCardAttributeParam *param = params->data;
+				const char *name = e_vcard_attribute_param_get_name (param);
+
+				if (!strcmp (name, EVC_TYPE)) {
+					GList *values = e_vcard_attribute_param_get_values (param);
+					if (values && values->data) {
+						gboolean matches = FALSE;
+
+						if (!found_needed1 && !strcasecmp ((char*)values->data, type_needed1)) {
+							found_needed1 = TRUE;
+							matches = TRUE;
+						}
+						else if (!found_needed2 && !strcasecmp ((char*)values->data, type_needed2)) {
+							found_needed2 = TRUE;
+							matches = TRUE;
+						}
+
+						if (!matches) {
+							/* this is to enforce that we find an attribute
+							   with *only* the TYPE='s we need.  This may seem like
+							   an odd restriction but it's the only way at present to
+							   implement the Other Fax and Other Phone attributes. */
+							found_needed1 =
+								found_needed2 = FALSE;
+							break;
+						}
+					}
+				}
+
+				if (found_needed1 && found_needed2) {
+					if (nth-- == 0)
+						return attr;
+					else
+						break;
+				}
+			}
+		}
+	}
+
+	return NULL;
+}
+
+static void
+e_contact_get_property (GObject *object,
+			guint prop_id,
+			GValue *value,
+			GParamSpec *pspec)
+{
+	const EContactFieldInfo *info = NULL;
+	gpointer data;
+	
+	if (prop_id < 1 || prop_id >= E_CONTACT_FIELD_LAST) {
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+		g_value_reset (value);
+		return;
+	}
+	
+	info = &field_info[prop_id];
+	data = e_contact_get (E_CONTACT (object), prop_id);
+
+	if (info->t & E_CONTACT_FIELD_TYPE_BOOLEAN) {
+		g_value_set_boolean (value, (gboolean)data);
+	} else if (info->t & E_CONTACT_FIELD_TYPE_LIST) {
+		g_value_set_pointer (value, data);
+#if 0
+	} else if (info->t & E_CONTACT_FIELD_TYPE_LIST_ELEM && info->t & E_CONTACT_FIELD_TYPE_STRING) {
+		g_value_set_string (value, data);
+	} else if (info->t & E_CONTACT_FIELD_TYPE_MULTI_ELEM && info->t & E_CONTACT_FIELD_TYPE_STRING) {
+		g_value_set_string (value, data);
+	} else if (info->t & E_CONTACT_FIELD_TYPE_ATTR_TYPE) {
+		if (info->t & E_CONTACT_FIELD_TYPE_STRING) {
+			g_value_set_string (value, data);
+		} else {
+			g_value_set_boxed (value, data);
+ 		}
+#endif
+	} else if (info->t & E_CONTACT_FIELD_TYPE_STRUCT) {
+		g_value_take_boxed (value, data);
+	} else if (info->t & E_CONTACT_FIELD_TYPE_GETSET) {
+		if (info->t & E_CONTACT_FIELD_TYPE_STRUCT) {
+			g_value_set_boxed (value, data);
+		} else {
+			g_value_set_string (value, data);
+ 		}
+	} else if (info->t & E_CONTACT_FIELD_TYPE_STRING) {
+		g_value_set_string (value, data);
+	} else {
+		g_value_set_pointer (value, data);
+ 	}
+}
+
+
+
+/**
+ * e_contact_new:
+ *
+ * Creates a new, blank #EContact.
+ *
+ * Return value: A new #EContact.
+ **/
+EContact*
+e_contact_new (void)
+{
+	return e_contact_new_from_vcard ("");
+}
+
+/**
+ * e_contact_new_from_vcard:
+ * @vcard: a string representing a vcard
+ * 
+ * Creates a new #EContact based on a vcard.
+ *
+ * Return value: A new #EContact.
+ **/
+EContact*
+e_contact_new_from_vcard  (const char *vcard)
+{
+	EContact *contact;
+	const gchar *file_as;
+
+	g_return_val_if_fail (vcard != NULL, NULL);
+
+	contact = g_object_new (E_TYPE_CONTACT, NULL);
+	e_vcard_construct (E_VCARD (contact), vcard);
+
+	/* Generate a FILE_AS field if needed */
+
+	file_as = e_contact_get_const (contact, E_CONTACT_FILE_AS);
+	if (!file_as || !*file_as) {
+		EContactName *name;
+		const gchar *org;
+		gchar *file_as_new = NULL;
+		gchar *strings [4];
+		gchar **strings_p = strings;
+
+		name = e_contact_get (contact, E_CONTACT_NAME);
+		org = e_contact_get_const (contact, E_CONTACT_ORG);
+
+		if (name) {
+			if (name->family && *name->family)
+				*(strings_p++) = name->family;
+			if (name->given && *name->given)
+				*(strings_p++) = name->given;
+
+			if (strings_p != strings) {
+				*strings_p = NULL;
+				file_as_new = g_strjoinv (", ", strings);
+			}
+
+			e_contact_name_free (name);
+		}
+
+		if (!file_as_new && org && *org)
+			file_as_new = g_strdup (org);
+
+		if (file_as_new) {
+			e_contact_set (contact, E_CONTACT_FILE_AS, file_as_new);
+			g_free (file_as_new);
+		}
+	}
+
+	return contact;
+}
+
+/**
+ * e_contact_duplicate:
+ * @contact: an #EContact
+ *
+ * Creates a copy of @contact.
+ *
+ * Return value: A new #EContact identical to @contact.
+ **/
+EContact*
+e_contact_duplicate (EContact *contact)
+{
+	char *vcard;
+	EContact *c;
+
+	g_return_val_if_fail (E_IS_CONTACT (contact), NULL);
+
+	vcard = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
+	c = e_contact_new_from_vcard (vcard);
+	g_free (vcard);
+
+	return c;
+}
+
+/**
+ * e_contact_field_name:
+ * @field_id: an #EContactField
+ *
+ * Gets the string representation of @field_id.
+ *
+ * Return value: The string representation of @field_id, or %NULL if it doesn't exist.
+ **/
+const char *
+e_contact_field_name (EContactField field_id)
+{
+	g_return_val_if_fail (field_id >= 1 && field_id <= E_CONTACT_FIELD_LAST, "");
+
+	return field_info[field_id].field_name;
+}
+
+/**
+ * e_contact_pretty_name:
+ * @field_id: an #EContactField
+ *
+ * Gets a human-readable, translated string representation
+ * of @field_id.
+ *
+ * Return value: The human-readable representation of @field_id, or %NULL if it doesn't exist.
+ **/
+const char *
+e_contact_pretty_name (EContactField field_id)
+{
+	g_return_val_if_fail (field_id >= 1 && field_id <= E_CONTACT_FIELD_LAST, "");
+
+#ifdef ENABLE_NLS
+	bindtextdomain (GETTEXT_PACKAGE, EVOLUTION_LOCALEDIR);
+	bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+#endif
+
+	return _(field_info[field_id].pretty_name);
+}
+
+/**
+ * e_contact_vcard_attribute:
+ * @field_id: an #EContactField
+ *
+ * Gets the vcard attribute corresponding to @field_id, as a string.
+ *
+ * Return value: The vcard attribute corresponding to @field_id, or %NULL if it doesn't exist.
+ **/
+const char*
+e_contact_vcard_attribute  (EContactField field_id)
+{
+	g_return_val_if_fail (field_id >= 1 && field_id <= E_CONTACT_FIELD_LAST, "");
+
+	return field_info[field_id].vcard_field_name;
+}
+
+/**
+ * e_contact_field_id:
+ * @field_name: a string representing a contact field
+ * 
+ * Gets the #EContactField corresponding to the @field_name.
+ *
+ * Return value: An #EContactField corresponding to @field_name, or %0 if it doesn't exist.
+ **/
+EContactField
+e_contact_field_id (const char *field_name)
+{
+	int i;
+
+	for (i = E_CONTACT_FIELD_FIRST; i < E_CONTACT_FIELD_LAST; i ++) {
+		if (!strcmp (field_info[i].field_name, field_name))
+			return field_info[i].field_id;
+	}
+
+	g_warning ("unknown field name `%s'", field_name);
+	return 0;
+}
+
+EContactField
+e_contact_field_id_from_vcard (const char *vcard_field)
+{
+	int i;
+
+	for (i = E_CONTACT_FIELD_FIRST; i < E_CONTACT_FIELD_LAST; i ++) {
+		if (field_info[i].vcard_field_name == NULL)
+			continue;
+		if (field_info[i].t & E_CONTACT_FIELD_TYPE_SYNTHETIC)
+			continue;
+		if (!strcmp (field_info[i].vcard_field_name, vcard_field))
+			return field_info[i].field_id;
+	}
+
+	g_warning ("unknown vCard field `%s'", vcard_field);
+	return 0;
+}
+
+/**
+ * e_contact_get:
+ * @contact: an #EContact
+ * @field_id: an #EContactField
+ *
+ * Gets the value of @contact's field specified by @field_id.
+ *
+ * Return value: Depends on the field's type, owned by the caller.
+ **/
+gpointer
+e_contact_get (EContact *contact, EContactField field_id)
+{
+	const EContactFieldInfo *info = NULL;
+ 
+	g_return_val_if_fail (contact && E_IS_CONTACT (contact), NULL);
+	g_return_val_if_fail (field_id >= 1 && field_id <= E_CONTACT_FIELD_LAST, NULL);
+
+	info = &field_info[field_id];
+
+	if (info->t & E_CONTACT_FIELD_TYPE_BOOLEAN) {
+		EVCardAttribute *attr = e_contact_get_first_attr (contact, info->vcard_field_name);
+		gboolean rv = FALSE;
+
+		if (attr) {
+			GList *v = e_vcard_attribute_get_values (attr);
+			rv = v && v->data && !g_ascii_strcasecmp ((char*)v->data, "true");
+			return (gpointer)rv;
+		}
+	}
+	else if (info->t & E_CONTACT_FIELD_TYPE_LIST) {
+		EVCardAttribute *attr = e_contact_get_first_attr (contact, info->vcard_field_name);
+
+		if (attr) {
+			GList *list = g_list_copy (e_vcard_attribute_get_values (attr));
+			GList *l;
+			for (l = list; l; l = l->next)
+				l->data = g_strdup (l->data);
+			return list;
+		}
+	}
+	else if (info->t & E_CONTACT_FIELD_TYPE_LIST_ELEM) {
+		if (info->t & E_CONTACT_FIELD_TYPE_STRING) {
+			EVCardAttribute *attr = e_contact_get_first_attr (contact, info->vcard_field_name);
+
+			if (attr) {
+				GList *v;
+				
+				v = e_vcard_attribute_get_values (attr);
+				v = g_list_nth (v, info->list_elem);
+				
+				return v ? g_strdup (v->data) : NULL;
+			}
+		}
+	}
+	else if (info->t & E_CONTACT_FIELD_TYPE_MULTI_ELEM) {
+		if (info->t & E_CONTACT_FIELD_TYPE_STRING) {
+			GList *attrs, *l;
+			int num_left = info->list_elem;
+
+			attrs = e_vcard_get_attributes (E_VCARD (contact));
+
+			for (l = attrs; l; l = l->next) {
+				EVCardAttribute *attr = l->data;
+				const char *name;
+
+				name = e_vcard_attribute_get_name (attr);
+
+				if (!strcmp (name, info->vcard_field_name)) {
+					if (num_left-- == 0) {
+						GList *v = e_vcard_attribute_get_values (attr);
+
+						return v ? g_strdup (v->data) : NULL;
+					}
+				}
+			}
+		}
+	}
+	else if (info->t & E_CONTACT_FIELD_TYPE_ATTR_TYPE) {
+		EVCardAttribute *attr = e_contact_find_attribute_with_types (contact, info->vcard_field_name, info->attr_type1, info->attr_type2, info->list_elem);
+
+		if (info->t & E_CONTACT_FIELD_TYPE_STRING) {
+			if (attr) {
+				GList *p = e_vcard_attribute_get_values (attr);
+				return g_strdup (p->data);
+			}
+			else {
+				return NULL;
+			}
+		}
+		else { /* struct */
+			return info->struct_getter (contact, attr);
+		}
+
+	}
+	else if (info->t & E_CONTACT_FIELD_TYPE_STRUCT) {
+		EVCardAttribute *attr = e_contact_get_first_attr (contact, info->vcard_field_name);
+		if (attr)
+			return info->struct_getter (contact, attr);
+	}
+	else if (info->t & E_CONTACT_FIELD_TYPE_GETSET) {
+		EVCardAttribute *attr = e_contact_get_first_attr (contact, info->vcard_field_name);
+		void *rv = NULL;
+
+		if (attr)
+			rv = info->struct_getter (contact, attr);
+
+		if (info->t & E_CONTACT_FIELD_TYPE_STRUCT)
+			return (gpointer)info->boxed_type_getter();
+		else
+			return g_strdup (rv);
+	}
+	else if (info->t & E_CONTACT_FIELD_TYPE_SYNTHETIC) {
+		switch (info->field_id) {
+		case E_CONTACT_NAME_OR_ORG: {
+			const char *str;
+
+			str = e_contact_get_const (contact, E_CONTACT_FILE_AS);
+			if (!str)
+				str = e_contact_get_const (contact, E_CONTACT_FULL_NAME);
+			if (!str)
+				str = e_contact_get_const (contact, E_CONTACT_ORG);
+			if (!str) {
+				gboolean is_list = GPOINTER_TO_INT (e_contact_get (contact, E_CONTACT_IS_LIST));
+
+				if (is_list)
+					str = _("Unnamed List");
+				else
+					str = e_contact_get_const (contact, E_CONTACT_EMAIL_1);
+			}
+
+			return g_strdup (str);
+		}
+		case E_CONTACT_CATEGORIES: {
+			EVCardAttribute *attr = e_contact_get_first_attr (contact, EVC_CATEGORIES);
+			char *rv = NULL;
+
+			if (attr) {
+				GString *str = g_string_new ("");
+				GList *v = e_vcard_attribute_get_values (attr);
+				while (v) {
+					g_string_append (str, (char*)v->data);
+					v = v->next;
+					if (v)
+						g_string_append_c (str, ',');
+				}
+
+				rv = g_string_free (str, FALSE);
+			}
+			return rv;
+			break;
+		}
+		default:
+			g_warning ("unhandled synthetic field 0x%02x", info->field_id);
+			break;
+		}
+	}
+	else {
+		GList *attrs, *l;
+		GList *rv = NULL; /* used for multi attribute lists */
+
+		attrs = e_vcard_get_attributes (E_VCARD (contact));
+
+		for (l = attrs; l; l = l->next) {
+			EVCardAttribute *attr = l->data;
+			const char *name;
+
+			name = e_vcard_attribute_get_name (attr);
+
+			if (!strcmp (name, info->vcard_field_name)) {
+				GList *v;
+				v = e_vcard_attribute_get_values (attr);
+
+				if (info->t & E_CONTACT_FIELD_TYPE_STRING) {
+					return v ? g_strdup (v->data) : NULL;
+				}
+				else {
+					rv = g_list_append (rv, v ? g_strdup (v->data) : NULL);
+				}
+			}
+		}
+		return rv;
+	}
+	return NULL;
+}
+
+static void
+free_const_data (gpointer data, GObject *where_object_was)
+{
+	g_free (data);
+}
+
+/**
+ * e_contact_get_const:
+ * @contact: an #EContact
+ * @field_id: an #EContactField
+ *
+ * Gets the value of @contact's field specified by @field_id, caching
+ * the result so it can be freed later.
+ *
+ * Return value: Depends on the field's type, owned by the #EContact.
+ **/
+gconstpointer
+e_contact_get_const (EContact *contact, EContactField field_id)
+{
+	gboolean is_string = FALSE;
+	gpointer value = NULL;
+
+	g_return_val_if_fail (E_IS_CONTACT (contact), NULL);
+	g_return_val_if_fail (field_id >= 1 && field_id <= E_CONTACT_LAST_SIMPLE_STRING, NULL);
+
+	if (field_info [field_id].t & E_CONTACT_FIELD_TYPE_STRING)
+		is_string = TRUE;
+
+	if (is_string)
+		value = contact->priv->cached_strings[field_id];
+
+	if (!value) {
+		value = e_contact_get (contact, field_id);
+		if (is_string && value)
+			contact->priv->cached_strings[field_id] = value;
+
+		if (value)
+			g_object_weak_ref (G_OBJECT (contact), free_const_data, value);
+	}
+
+	return value;
+}
+
+/**
+ * e_contact_set;
+ * @contact: an #EContact
+ * @field_id: an #EContactField
+ * @value: a value whose type depends on the @field_id
+ *
+ * Sets the value of @contact's field specified by @field_id to @value.
+ **/
+void
+e_contact_set (EContact *contact, EContactField field_id, gpointer value)
+{
+	d(printf ("e_contact_set (%p, %d, %p)\n", contact, field_id, value));
+
+	g_return_if_fail (contact && E_IS_CONTACT (contact));
+	g_return_if_fail (field_id >= 1 && field_id <= E_CONTACT_FIELD_LAST);
+
+	/* set the cached slot to NULL so we'll re-get the new string
+	   if e_contact_get_const is called again */
+	contact->priv->cached_strings[field_id] = NULL;
+
+	g_object_set (contact,
+		      e_contact_field_name (field_id), value,
+		      NULL);
+}
+
+/**
+ * e_contact_get_attributes:
+ * @contact: an #EContact
+ * @field_id: an #EContactField
+ *
+ * Gets a list of the vcard attributes for @contact's @field_id.
+ *
+ * Return value: A #GList of pointers to #EVCardAttribute, owned by the caller.
+ **/
+GList*
+e_contact_get_attributes (EContact *contact, EContactField field_id)
+{
+	GList *l = NULL;
+	GList *attrs, *a;
+	const EContactFieldInfo *info = NULL;
+
+	g_return_val_if_fail (contact && E_IS_CONTACT (contact), NULL);
+	g_return_val_if_fail (field_id >= 1 && field_id <= E_CONTACT_FIELD_LAST, NULL);
+
+	info = &field_info[field_id];
+
+	attrs = e_vcard_get_attributes (E_VCARD (contact));
+
+	for (a = attrs; a; a = a->next) {
+		EVCardAttribute *attr = a->data;
+		const char *name;
+
+		name = e_vcard_attribute_get_name (attr);
+
+		if (!strcmp (name, info->vcard_field_name)) {
+			l = g_list_prepend (l, e_vcard_attribute_copy (attr));
+		}
+	}
+
+	return g_list_reverse(l);
+}
+
+/**
+ * e_contact_set_attributes:
+ * @contact: an #EContact
+ * @field_id: an #EContactField
+ * @attributes: a #GList of pointers to #EVCardAttribute
+ *
+ * Sets the vcard attributes for @contact's @field_id.
+ **/
+void
+e_contact_set_attributes (EContact *contact, EContactField field_id, GList *attributes)
+{
+	const EContactFieldInfo *info = NULL;
+	GList *l;
+
+	g_return_if_fail (contact && E_IS_CONTACT (contact));
+	g_return_if_fail (field_id >= 1 && field_id <= E_CONTACT_FIELD_LAST);
+
+	info = &field_info[field_id];
+
+	e_vcard_remove_attributes (E_VCARD (contact), NULL, info->vcard_field_name);
+
+	for (l = attributes; l; l = l->next)
+		e_vcard_add_attribute (E_VCARD (contact),
+				       e_vcard_attribute_copy ((EVCardAttribute*)l->data));
+}
+
+/**
+ * e_contact_name_new:
+ *
+ * Creates a new #EContactName struct.
+ *
+ * Return value: A new #EContactName struct.
+ **/
+EContactName*
+e_contact_name_new ()
+{
+	return g_new0 (EContactName, 1);
+}
+
+/**
+ * e_contact_name_to_string:
+ * @name: an #EContactName
+ *
+ * Generates a string representation of @name.
+ *
+ * Return value: The string representation of @name.
+ **/
+char *
+e_contact_name_to_string(const EContactName *name)
+{
+	char *strings[6], **stringptr = strings;
+
+	g_return_val_if_fail (name != NULL, NULL);
+
+	if (name->prefixes && *name->prefixes)
+		*(stringptr++) = name->prefixes;
+	if (name->given && *name->given)
+		*(stringptr++) = name->given;
+	if (name->additional && *name->additional)
+		*(stringptr++) = name->additional;
+	if (name->family && *name->family)
+		*(stringptr++) = name->family;
+	if (name->suffixes && *name->suffixes)
+		*(stringptr++) = name->suffixes;
+	*stringptr = NULL;
+	return g_strjoinv(" ", strings);
+}
+
+/**
+ * e_contact_name_from_string:
+ * @name_str: a string representing a contact's full name
+ *
+ * Creates a new #EContactName based on the parsed @name_str.
+ *
+ * Return value: A new #EContactName struct.
+ **/
+EContactName*
+e_contact_name_from_string (const char *name_str)
+{
+	EContactName *name = e_contact_name_new();
+	ENameWestern *western;
+
+	g_return_val_if_fail (name_str != NULL, NULL);
+
+	western = e_name_western_parse (name_str ? name_str : "");
+	
+	name->prefixes   = g_strdup (western->prefix);
+	name->given      = g_strdup (western->first );
+	name->additional = g_strdup (western->middle);
+	name->family     = g_strdup (western->last  );
+	name->suffixes   = g_strdup (western->suffix);
+	
+	e_name_western_free(western);
+	
+	return name;
+}
+
+/**
+ * e_contact_name_copy:
+ * @n: an #EContactName
+ *
+ * Creates a copy of @n.
+ *
+ * Return value: A new #EContactName identical to @n.
+ **/
+EContactName*
+e_contact_name_copy (EContactName *n)
+{
+	EContactName *name;
+
+	g_return_val_if_fail (n != NULL, NULL);
+
+	name = e_contact_name_new();
+	
+	name->prefixes   = g_strdup (n->prefixes);
+	name->given      = g_strdup (n->given);
+	name->additional = g_strdup (n->additional);
+	name->family     = g_strdup (n->family);
+	name->suffixes   = g_strdup (n->suffixes);
+	
+	return name;
+}
+
+/**
+ * e_contact_name_free:
+ * @name: an #EContactName
+ *
+ * Frees @name and its contents.
+ **/
+void
+e_contact_name_free (EContactName *name)
+{
+	if (!name)
+		return;
+
+	g_free (name->family);
+	g_free (name->given);
+	g_free (name->additional);
+	g_free (name->prefixes);
+	g_free (name->suffixes);
+
+	g_free (name);
+}
+
+GType
+e_contact_name_get_type (void)
+{
+	static GType type_id = 0;
+
+	if (!type_id)
+		type_id = g_boxed_type_register_static ("EContactName",
+							(GBoxedCopyFunc) e_contact_name_copy,
+							(GBoxedFreeFunc) e_contact_name_free);
+	return type_id;
+}
+
+/**
+ * e_contact_date_from_string:
+ * @str: a date string in the format YYYY-MM-DD or YYYYMMDD
+ *
+ * Creates a new #EContactDate based on @str.
+ *
+ * Return value: A new #EContactDate struct.
+ **/
+EContactDate*
+e_contact_date_from_string (const char *str)
+{
+	EContactDate* date;
+	int length;
+
+	g_return_val_if_fail (str != NULL, NULL);
+
+	date = e_contact_date_new();
+	length = strlen(str);
+	
+	if (length == 10 ) {
+		date->year = str[0] * 1000 + str[1] * 100 + str[2] * 10 + str[3] - '0' * 1111;
+		date->month = str[5] * 10 + str[6] - '0' * 11;
+		date->day = str[8] * 10 + str[9] - '0' * 11;
+	} else if ( length == 8 ) {
+		date->year = str[0] * 1000 + str[1] * 100 + str[2] * 10 + str[3] - '0' * 1111;
+		date->month = str[4] * 10 + str[5] - '0' * 11;
+		date->day = str[6] * 10 + str[7] - '0' * 11;
+	}
+	
+	return date;
+}
+
+/**
+ * e_contact_date_to_string:
+ * @dt: an #EContactDate
+ *
+ * Generates a date string in the format YYYY-MM-DD based
+ * on the values of @dt.
+ *
+ * Return value: A date string, owned by the caller.
+ **/
+char *
+e_contact_date_to_string (EContactDate *dt)
+{
+	if (dt) 
+		return g_strdup_printf ("%04d-%02d-%02d",
+					CLAMP(dt->year, 1000, 9999),
+					CLAMP(dt->month, 1, 12),
+					CLAMP(dt->day, 1, 31));
+	else
+		return NULL;
+}
+
+/**
+ * e_contact_date_equal:
+ * @dt1: an #EContactDate
+ * @dt2: an #EContactDate
+ *
+ * Checks if @dt1 and @dt2 are the same date.
+ *
+ * Return value: %TRUE if @dt1 and @dt2 are equal, %FALSE otherwise.
+ **/
+gboolean
+e_contact_date_equal (EContactDate *dt1, EContactDate *dt2)
+{
+	if (dt1 && dt2) {
+		return (dt1->year == dt2->year &&
+			dt1->month == dt2->month &&
+			dt1->day == dt2->day);
+	} else
+		return (!!dt1 == !!dt2);
+}
+
+/**
+ * e_contact_date_copy:
+ * @dt: an #EContactDate
+ *
+ * Creates a copy of @dt.
+ *
+ * Return value: A new #EContactDate struct identical to @dt.
+ **/
+static EContactDate *
+e_contact_date_copy (EContactDate *dt)
+{
+	EContactDate *dt2 = e_contact_date_new ();
+	dt2->year = dt->year;
+	dt2->month = dt->month;
+	dt2->day = dt->day;
+
+	return dt2;
+}
+
+/**
+ * e_contact_date_free:
+ * @dt: an #EContactDate
+ *
+ * Frees the @dt struct and its contents.
+ **/
+void
+e_contact_date_free (EContactDate *dt)
+{
+	g_free (dt);
+}
+
+GType
+e_contact_date_get_type (void)
+{
+	static GType type_id = 0;
+
+	if (!type_id)
+		type_id = g_boxed_type_register_static ("EContactDate",
+							(GBoxedCopyFunc) e_contact_date_copy,
+							(GBoxedFreeFunc) e_contact_date_free);
+	return type_id;
+}
+
+/**
+ * e_contact_date_new:
+ * 
+ * Creates a new #EContactDate struct.
+ *
+ * Return value: A new #EContactDate struct.
+ **/
+EContactDate*
+e_contact_date_new (void)
+{
+	return g_new0 (EContactDate, 1);
+}
+
+/**
+ * e_contact_photo_free:
+ * @photo: an #EContactPhoto struct
+ *
+ * Frees the @photo struct and its contents.
+ **/
+void
+e_contact_photo_free (EContactPhoto *photo)
+{
+	if (!photo)
+		return;
+	
+	switch (photo->type) {
+	case E_CONTACT_PHOTO_TYPE_INLINED:
+		g_free (photo->data.inlined.mime_type);
+		g_free (photo->data.inlined.data);
+		break;
+	case E_CONTACT_PHOTO_TYPE_URI:
+		g_free (photo->data.uri);
+		break;
+	default:
+		g_warning ("Unknown EContactPhotoType %d", photo->type);
+		break;
+	}
+
+	g_free (photo);
+}
+
+/**
+ * e_contact_photo_copy:
+ * @photo: an #EContactPhoto
+ *
+ * Creates a copy of @photo.
+ *
+ * Return value: A new #EContactPhoto struct identical to @photo.
+ **/
+static EContactPhoto *
+e_contact_photo_copy (EContactPhoto *photo)
+{
+	EContactPhoto *photo2 = g_new0 (EContactPhoto, 1);
+	switch (photo->type) {
+	case E_CONTACT_PHOTO_TYPE_INLINED:
+		photo2->type = E_CONTACT_PHOTO_TYPE_INLINED;
+		photo2->data.inlined.mime_type = g_strdup (photo->data.inlined.mime_type);
+		photo2->data.inlined.length = photo->data.inlined.length;
+		photo2->data.inlined.data = g_malloc (photo2->data.inlined.length);
+		memcpy (photo2->data.inlined.data, photo->data.inlined.data, photo->data.inlined.length);
+		break;
+	case E_CONTACT_PHOTO_TYPE_URI:
+		photo2->type = E_CONTACT_PHOTO_TYPE_URI;
+		photo2->data.uri = g_strdup (photo->data.uri);
+		break;
+	default:
+		g_warning ("Unknown EContactPhotoType %d", photo->type);
+		break;
+	}
+	return photo2;
+}
+
+GType
+e_contact_photo_get_type (void)
+{
+	static GType type_id = 0;
+
+	if (!type_id)
+		type_id = g_boxed_type_register_static ("EContactPhoto",
+							(GBoxedCopyFunc) e_contact_photo_copy,
+							(GBoxedFreeFunc) e_contact_photo_free);
+	return type_id;
+}
+
+/**
+ * e_contact_address_free:
+ * @address: an #EContactAddress
+ *
+ * Frees the @address struct and its contents.
+ **/
+void
+e_contact_address_free (EContactAddress *address)
+{
+	if (!address)
+		return;
+
+	g_free (address->address_format);
+	g_free (address->po);
+	g_free (address->ext);
+	g_free (address->street);
+	g_free (address->locality);
+	g_free (address->region);
+	g_free (address->code);
+	g_free (address->country);
+
+	g_free (address);
+}
+
+static EContactAddress *
+e_contact_address_copy (EContactAddress *address)
+{
+	EContactAddress *address2 = g_new0 (EContactAddress, 1);
+
+	address2->address_format = g_strdup (address->address_format);
+	address2->po = g_strdup (address->po);
+	address2->ext = g_strdup (address->ext);
+	address2->street = g_strdup (address->street);
+	address2->locality = g_strdup (address->locality);
+	address2->region = g_strdup (address->region);
+	address2->code = g_strdup (address->code);
+	address2->country = g_strdup (address->country);
+
+	return address2;
+}
+
+GType
+e_contact_address_get_type (void)
+{
+	static GType type_id = 0;
+
+	if (!type_id)
+		type_id = g_boxed_type_register_static ("EContactAddress",
+							(GBoxedCopyFunc) e_contact_address_copy,
+							(GBoxedFreeFunc) e_contact_address_free);
+	return type_id;
+}
+
+/**
+ * e_contact_cert_free:
+ * @cert: an #EContactCert
+ *
+ * Frees the @cert struct and its contents.
+ **/
+void
+e_contact_cert_free (EContactCert *cert)
+{
+	if (!cert)
+		return;
+
+	g_free (cert->data);
+	g_free (cert);
+}
+
+static EContactCert *
+e_contact_cert_copy (EContactCert *cert)
+{
+	EContactCert *cert2 = g_new0 (EContactCert, 1);
+	cert2->length = cert->length;
+	cert2->data = g_malloc (cert2->length);
+	memcpy (cert2->data, cert->data, cert->length);
+
+	return cert2;
+}
+
+GType
+e_contact_cert_get_type (void)
+{
+	static GType type_id = 0;
+
+	if (!type_id)
+		type_id = g_boxed_type_register_static ("EContactCert",
+							(GBoxedCopyFunc) e_contact_cert_copy,
+							(GBoxedFreeFunc) e_contact_cert_free);
+	return type_id;
+}
Index: addressbook/libebook-dbus/e-book-query.c
===================================================================
--- addressbook/libebook-dbus/e-book-query.c	(revision 409)
+++ addressbook/libebook-dbus/e-book-query.c	(working copy)
@@ -1 +1,713 @@
-link ../libebook-orbit/./e-book-query.c
\ No newline at end of file
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+#include <config.h>
+
+#include "e-book-query.h"
+#include <libedataserver/e-sexp.h>
+
+#include <stdarg.h>
+#include <string.h>
+
+typedef enum {
+	E_BOOK_QUERY_TYPE_AND,
+	E_BOOK_QUERY_TYPE_OR,
+	E_BOOK_QUERY_TYPE_NOT,
+	E_BOOK_QUERY_TYPE_FIELD_EXISTS,
+	E_BOOK_QUERY_TYPE_FIELD_TEST,
+	E_BOOK_QUERY_TYPE_ANY_FIELD_CONTAINS
+} EBookQueryType;
+
+struct EBookQuery {
+	EBookQueryType type;
+	int ref_count;
+
+	union {
+		struct {
+			guint          nqs;
+			EBookQuery   **qs;
+		} andor;
+
+		struct {
+			EBookQuery    *q;
+		} not;
+
+		struct {
+			EBookQueryTest test;
+			EContactField  field;
+			char          *value;
+		} field_test;
+
+		struct {
+			EContactField  field;
+			char          *vcard_field;
+		} exist;
+
+		struct {
+			char          *value;
+		} any_field_contains;
+	} query;
+};
+
+static EBookQuery *
+conjoin (EBookQueryType type, int nqs, EBookQuery **qs, gboolean unref)
+{
+	EBookQuery *ret = g_new0 (EBookQuery, 1);
+	int i;
+
+	ret->type = type;
+	ret->query.andor.nqs = nqs;
+	ret->query.andor.qs = g_new (EBookQuery *, nqs);
+	for (i = 0; i < nqs; i++) {
+		ret->query.andor.qs[i] = qs[i];
+		if (!unref)
+			e_book_query_ref (qs[i]);
+	}
+
+	return ret;
+}
+
+/**
+ * e_book_query_and:
+ * @nqs: the number of queries to AND
+ * @qs: pointer to an array of #EBookQuery items
+ * @unref: if %TRUE, the new query takes ownership of the existing queries
+ *
+ * Create a new #EBookQuery which is the logical AND of the queries in #qs.
+ *
+ * Return value: A new #EBookQuery
+ **/
+EBookQuery *
+e_book_query_and (int nqs, EBookQuery **qs, gboolean unref)
+{
+	return conjoin (E_BOOK_QUERY_TYPE_AND, nqs, qs, unref);
+}
+
+/**
+ * e_book_query_or:
+ * @nqs: the number of queries to OR
+ * @qs: pointer to an array of #EBookQuery items
+ * @unref: if #TRUE, the new query takes ownership of the existing queries
+ *
+ * Creates a new #EBookQuery which is the logical OR of the queries in #qs.
+ *
+ * Return value: A new #EBookQuery
+ **/
+EBookQuery *
+e_book_query_or (int nqs, EBookQuery **qs, gboolean unref)
+{
+	return conjoin (E_BOOK_QUERY_TYPE_OR, nqs, qs, unref);
+}
+
+static EBookQuery *
+conjoinv (EBookQueryType type, EBookQuery *q, va_list ap)
+{
+	EBookQuery *ret = g_new0 (EBookQuery, 1);
+	GPtrArray *qs;
+
+	qs = g_ptr_array_new ();
+	while (q) {
+		g_ptr_array_add (qs, q);
+		q = va_arg (ap, EBookQuery *);
+	}
+	va_end (ap);
+
+	ret->type = type;
+	ret->query.andor.nqs = qs->len;
+	ret->query.andor.qs = (EBookQuery **)qs->pdata;
+	g_ptr_array_free (qs, FALSE);
+
+	return ret;
+}
+
+/**
+ * e_book_query_andv:
+ * @q: first #EBookQuery
+ * @Varargs: #NULL terminated list of #EBookQuery pointers
+ *
+ * Creates a new #EBookQuery which is the logical AND of the queries specified.
+ *
+ * Return value: A new #EBookQuery
+ **/
+EBookQuery *
+e_book_query_andv (EBookQuery *q, ...)
+{
+	va_list ap;
+
+	va_start (ap, q);
+	return conjoinv (E_BOOK_QUERY_TYPE_AND, q, ap);
+}
+
+/**
+ * e_book_query_orv:
+ * @q: first #EBookQuery
+ * @Varargs: #NULL terminated list of #EBookQuery pointers
+ *
+ * Creates a new #EBookQuery which is the logical OR of the queries specified.
+ *
+ * Return value: A new #EBookQuery
+ **/
+EBookQuery *
+e_book_query_orv (EBookQuery *q, ...)
+{
+	va_list ap;
+
+	va_start (ap, q);
+	return conjoinv (E_BOOK_QUERY_TYPE_OR, q, ap);
+}
+
+/**
+ * e_book_query_not:
+ * @q: an #EBookQuery
+ * @unref: if #TRUE, the new query takes ownership of the existing queries
+ *
+ * Creates a new #EBookQuery which is the opposite of #q.
+ *
+ * Return value: the new #EBookQuery
+ **/
+EBookQuery *
+e_book_query_not (EBookQuery *q, gboolean unref)
+{
+	EBookQuery *ret = g_new0 (EBookQuery, 1);
+
+	ret->type = E_BOOK_QUERY_TYPE_NOT;
+	ret->query.not.q = q;
+	if (!unref)
+		e_book_query_ref (q);
+
+	return ret;
+}
+
+/**
+ * e_book_query_field_test:
+ * @field: an #EContactField to test
+ * @test: the test to apply
+ * @value: the value to test for
+ *
+ * Creates a new #EBookQuery which tests @field for @value using the test @test.
+ *
+ * Return value: the new #EBookQuery
+ **/
+EBookQuery *
+e_book_query_field_test (EContactField field,
+			 EBookQueryTest test,
+			 const char *value)
+{
+	EBookQuery *ret = g_new0 (EBookQuery, 1);
+
+	ret->type = E_BOOK_QUERY_TYPE_FIELD_TEST;
+	ret->query.field_test.field = field;
+	ret->query.field_test.test = test;
+	ret->query.field_test.value = g_strdup (value);
+
+	return ret;
+}
+
+/**
+ * e_book_query_field_exists:
+ * @field: a #EContactField
+ *
+ * Creates a new #EBookQuery which tests if the field @field exists.
+ * Return value: the new #EBookQuery
+ **/
+EBookQuery *
+e_book_query_field_exists (EContactField field)
+{
+	EBookQuery *ret = g_new0 (EBookQuery, 1);
+
+	ret->type = E_BOOK_QUERY_TYPE_FIELD_EXISTS;
+	ret->query.exist.field = field;
+	ret->query.exist.vcard_field = NULL;
+
+	return ret;
+}
+
+/**
+ * e_book_query_vcard_field_exists:
+ * @field: a field name
+ *
+ * Creates a new #EBookQuery which tests if the field @field exists. @field
+ * should be a vCard field name, such as #FN or #X-MSN.
+ * Return value: the new #EBookQuery
+ **/
+EBookQuery *
+e_book_query_vcard_field_exists (const char *field)
+{
+	EBookQuery *ret = g_new0 (EBookQuery, 1);
+
+	ret->type = E_BOOK_QUERY_TYPE_FIELD_EXISTS;
+	ret->query.exist.field = 0;
+	ret->query.exist.vcard_field = g_strdup (field);
+
+	return ret;
+}
+
+/**
+ * e_book_query_any_field_contains:
+ * @value: a value
+ *
+ * Creates a new #EBookQuery which tests if any field contains @value.
+ *
+ * Return value: the new #EBookQuery
+ **/
+EBookQuery *
+e_book_query_any_field_contains (const char *value)
+{
+	EBookQuery *ret = g_new0 (EBookQuery, 1);
+
+	ret->type = E_BOOK_QUERY_TYPE_ANY_FIELD_CONTAINS;
+	ret->query.any_field_contains.value = g_strdup (value);
+
+	return ret;
+}
+
+/**
+ * e_book_query_unref:
+ * @q: an #EBookQuery
+ *
+ * Decrement the reference count on @q. When the reference count reaches 0, @q
+ * will be freed and any child queries will have e_book_query_unref() called.
+ **/
+void
+e_book_query_unref (EBookQuery *q)
+{
+	int i;
+
+	if (q->ref_count--)
+		return;
+
+	switch (q->type) {
+	case E_BOOK_QUERY_TYPE_AND:
+	case E_BOOK_QUERY_TYPE_OR:
+		for (i = 0; i < q->query.andor.nqs; i++)
+			e_book_query_unref (q->query.andor.qs[i]);
+		g_free (q->query.andor.qs);
+		break;
+
+	case E_BOOK_QUERY_TYPE_NOT:
+		e_book_query_unref (q->query.not.q);
+		break;
+
+	case E_BOOK_QUERY_TYPE_FIELD_TEST:
+		g_free (q->query.field_test.value);
+		break;
+
+	case E_BOOK_QUERY_TYPE_FIELD_EXISTS:
+		g_free (q->query.exist.vcard_field);
+		break;
+
+	case E_BOOK_QUERY_TYPE_ANY_FIELD_CONTAINS:
+		g_free (q->query.any_field_contains.value);
+		break;
+
+	default:
+		break;
+	}
+
+	g_free (q);
+}
+
+/**
+ * e_book_query_ref:
+ * @q: a #EBookQuery
+ *
+ * Increment the reference count on @q.
+ * Return value: @q
+ **/
+EBookQuery *
+e_book_query_ref (EBookQuery *q)
+{
+	q->ref_count++;
+	return q;
+}
+
+static ESExpResult *
+func_and(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
+{
+	GList **list = data;
+	ESExpResult *r;
+	EBookQuery **qs;
+
+	if (argc > 0) {
+		int i;
+
+		qs = g_new0(EBookQuery*, argc);
+		
+		for (i = 0; i < argc; i ++) {
+			GList *list_head = *list;
+			if (!list_head)
+				break;
+			qs[i] = list_head->data;
+			*list = g_list_delete_link(*list, list_head);
+		}
+
+		*list = g_list_prepend(*list, 
+				       e_book_query_and (argc, qs, TRUE));
+
+		g_free (qs);
+	}
+
+	r = e_sexp_result_new(f, ESEXP_RES_BOOL);
+	r->value.bool = FALSE;
+
+	return r;
+}
+
+static ESExpResult *
+func_or(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
+{
+	GList **list = data;
+	ESExpResult *r;
+	EBookQuery **qs;
+
+	if (argc > 0) {
+		int i;
+
+		qs = g_new0(EBookQuery*, argc);
+		
+		for (i = 0; i < argc; i ++) {
+			GList *list_head = *list;
+			if (!list_head)
+				break;
+			qs[i] = list_head->data;
+			*list = g_list_delete_link(*list, list_head);
+		}
+
+		*list = g_list_prepend(*list, 
+				       e_book_query_or (argc, qs, TRUE));
+
+		g_free (qs);
+	}
+
+	r = e_sexp_result_new(f, ESEXP_RES_BOOL);
+	r->value.bool = FALSE;
+
+	return r;
+}
+
+static ESExpResult *
+func_not(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
+{
+	GList **list = data;
+	ESExpResult *r;
+
+	/* just replace the head of the list with the NOT of it. */
+	if (argc > 0) {
+		EBookQuery *term = (*list)->data;
+		(*list)->data = e_book_query_not (term, TRUE);
+	}
+
+	r = e_sexp_result_new(f, ESEXP_RES_BOOL);
+	r->value.bool = FALSE;
+
+	return r;
+}
+
+static ESExpResult *
+func_contains(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
+{
+	GList **list = data;
+	ESExpResult *r;
+
+	if (argc == 2
+	    && argv[0]->type == ESEXP_RES_STRING
+	    && argv[1]->type == ESEXP_RES_STRING) {
+		char *propname = argv[0]->value.string;
+		char *str = argv[1]->value.string;
+
+		if (!strcmp (propname, "x-evolution-any-field")) {
+			*list = g_list_prepend (*list, e_book_query_any_field_contains (str));
+		}
+		else {
+			EContactField field = e_contact_field_id (propname);
+
+			if (field)
+				*list = g_list_prepend (*list, e_book_query_field_test (field,
+											E_BOOK_QUERY_CONTAINS,
+											str));
+		}
+	}
+
+	r = e_sexp_result_new(f, ESEXP_RES_BOOL);
+	r->value.bool = FALSE;
+
+	return r;
+}
+
+static ESExpResult *
+func_is(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
+{
+	GList **list = data;
+	ESExpResult *r;
+
+	if (argc == 2
+	    && argv[0]->type == ESEXP_RES_STRING
+	    && argv[1]->type == ESEXP_RES_STRING) {
+		char *propname = argv[0]->value.string;
+		char *str = argv[1]->value.string;
+		EContactField field = e_contact_field_id (propname);
+
+		if (field)
+			*list = g_list_prepend (*list, e_book_query_field_test (field,
+										E_BOOK_QUERY_IS,
+										str));
+	}
+
+	r = e_sexp_result_new(f, ESEXP_RES_BOOL);
+	r->value.bool = FALSE;
+
+	return r;
+}
+
+static ESExpResult *
+func_beginswith(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
+{
+	GList **list = data;
+	ESExpResult *r;
+
+	if (argc == 2
+	    && argv[0]->type == ESEXP_RES_STRING
+	    && argv[1]->type == ESEXP_RES_STRING) {
+		char *propname = argv[0]->value.string;
+		char *str = argv[1]->value.string;
+		EContactField field = e_contact_field_id (propname);
+
+		if (field)
+			*list = g_list_prepend (*list, e_book_query_field_test (field,
+										E_BOOK_QUERY_BEGINS_WITH,
+										str));
+	}
+
+	r = e_sexp_result_new(f, ESEXP_RES_BOOL);
+	r->value.bool = FALSE;
+
+	return r;
+}
+
+static ESExpResult *
+func_endswith(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
+{
+	GList **list = data;
+	ESExpResult *r;
+
+	if (argc == 2
+	    && argv[0]->type == ESEXP_RES_STRING
+	    && argv[1]->type == ESEXP_RES_STRING) {
+		char *propname = argv[0]->value.string;
+		char *str = argv[1]->value.string;
+		EContactField field = e_contact_field_id (propname);
+
+		if (field)
+			*list = g_list_prepend (*list, e_book_query_field_test (field,
+										E_BOOK_QUERY_ENDS_WITH,
+										str));
+	}
+
+	r = e_sexp_result_new(f, ESEXP_RES_BOOL);
+	r->value.bool = FALSE;
+
+	return r;
+}
+
+static ESExpResult *
+func_exists(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
+{
+	GList **list = data;
+	ESExpResult *r;
+
+	if (argc == 1
+	    && argv[0]->type == ESEXP_RES_STRING) {
+		char *propname = argv[0]->value.string;
+		EContactField field = e_contact_field_id (propname);
+
+		if (field)
+			*list = g_list_prepend (*list, e_book_query_field_exists (field));
+	}
+
+	r = e_sexp_result_new(f, ESEXP_RES_BOOL);
+	r->value.bool = FALSE;
+
+	return r;
+}
+
+/* 'builtin' functions */
+static const struct {
+	char *name;
+	ESExpFunc *func;
+	int type;		/* set to 1 if a function can perform shortcut evaluation, or
+				   doesn't execute everything, 0 otherwise */
+} symbols[] = {
+	{ "and", func_and, 0 },
+	{ "or", func_or, 0 },
+	{ "not", func_not, 0 },
+	{ "contains", func_contains, 0 },
+	{ "is", func_is, 0 },
+	{ "beginswith", func_beginswith, 0 },
+	{ "endswith", func_endswith, 0 },
+	{ "exists", func_exists, 0 },
+};
+
+/**
+ * e_book_query_from_string:
+ * @query_string: the query
+ *
+ * Parse @query_string and return a new #EBookQuery representing it.
+ *
+ * Return value: the new #EBookValue
+ **/
+EBookQuery*
+e_book_query_from_string  (const char *query_string)
+{
+	ESExp *sexp;
+	ESExpResult *r;
+	EBookQuery *retval;
+	GList *list = NULL;
+	int i;
+
+	sexp = e_sexp_new();
+
+	for(i=0;i<sizeof(symbols)/sizeof(symbols[0]);i++) {
+		if (symbols[i].type == 1) {
+			e_sexp_add_ifunction(sexp, 0, symbols[i].name,
+					     (ESExpIFunc *)symbols[i].func, &list);
+		} else {
+			e_sexp_add_function(sexp, 0, symbols[i].name,
+					    symbols[i].func, &list);
+		}
+	}
+
+	e_sexp_input_text(sexp, query_string, strlen(query_string));
+	e_sexp_parse(sexp);
+
+	r = e_sexp_eval(sexp);
+
+	e_sexp_result_free(sexp, r);
+	e_sexp_unref (sexp);
+
+	if (list) {
+		if (list->next) {
+			g_warning ("conversion to EBookQuery");
+			retval = NULL;
+			g_list_foreach (list, (GFunc)e_book_query_unref, NULL);
+		}
+		else {
+			retval = list->data;
+		}
+	}
+	else {
+		g_warning ("conversion to EBookQuery failed");
+		retval = NULL;
+	}
+
+	g_list_free (list);
+	return retval;
+}
+
+/**
+ * e_book_query_to_string:
+ * @q: an #EBookQuery
+ *
+ * Return the string representation of @q.
+ *
+ * Return value: The string form of the query. This string should be freed when
+ * finished with.
+ **/
+char*
+e_book_query_to_string    (EBookQuery *q)
+{
+	GString *str = g_string_new ("(");
+	GString *encoded = g_string_new ("");
+	int i;
+	char *s = NULL;
+
+	switch (q->type) {
+	case E_BOOK_QUERY_TYPE_AND:
+		g_string_append (str, "and ");
+		for (i = 0; i < q->query.andor.nqs; i ++) {
+			s = e_book_query_to_string (q->query.andor.qs[i]);
+			g_string_append (str, s);
+			g_free (s);
+			g_string_append_c (str, ' ');
+		}
+		break;
+	case E_BOOK_QUERY_TYPE_OR:
+		g_string_append (str, "or ");
+		for (i = 0; i < q->query.andor.nqs; i ++) {
+			s = e_book_query_to_string (q->query.andor.qs[i]);
+			g_string_append (str, s);
+			g_free (s);
+			g_string_append_c (str, ' ');
+		}
+		break;
+	case E_BOOK_QUERY_TYPE_NOT:
+		s = e_book_query_to_string (q->query.not.q);
+		g_string_append_printf (str, "not %s", s);
+		g_free (s);
+		break;
+	case E_BOOK_QUERY_TYPE_FIELD_EXISTS:
+		if (q->query.exist.vcard_field) {
+			g_string_append_printf (str, "exists_vcard \"%s\"", q->query.exist.vcard_field);
+		} else {
+			g_string_append_printf (str, "exists \"%s\"", e_contact_field_name (q->query.exist.field));
+		}
+		break;
+	case E_BOOK_QUERY_TYPE_FIELD_TEST:
+		switch (q->query.field_test.test) {
+		case E_BOOK_QUERY_IS: s = "is"; break;
+		case E_BOOK_QUERY_CONTAINS: s = "contains"; break;
+		case E_BOOK_QUERY_BEGINS_WITH: s = "beginswith"; break;
+		case E_BOOK_QUERY_ENDS_WITH: s = "endswith"; break;
+		default:
+			g_assert_not_reached();
+			break;
+		}
+
+		e_sexp_encode_string (encoded, q->query.field_test.value);
+
+		g_string_append_printf (str, "%s \"%s\" %s",
+					s,
+					e_contact_field_name (q->query.field_test.field),
+					encoded->str);
+		break;
+	case E_BOOK_QUERY_TYPE_ANY_FIELD_CONTAINS:
+		g_string_append_printf (str, "contains \"x-evolution-any-field\"");
+		e_sexp_encode_string (str, q->query.any_field_contains.value);
+		break;
+	}
+	 
+
+	g_string_append (str, ")");
+
+	g_string_free (encoded, TRUE);
+
+	return g_string_free (str, FALSE);
+}
+
+GType
+e_book_query_get_type (void)
+{
+	static GType type_id = 0;
+
+	if (!type_id)
+		type_id = g_boxed_type_register_static ("EBookQuery",
+							(GBoxedCopyFunc) e_book_query_copy,
+							(GBoxedFreeFunc) e_book_query_unref);
+	return type_id;
+}
+
+/**
+ * e_book_query_oopy:
+ * @q: an #EBookQuery
+ *
+ * Creates a copy of @q.
+ *
+ * Return value: A new #EBookQuery identical to @q.
+ **/
+EBookQuery*
+e_book_query_copy (EBookQuery *q)
+{
+	char *str = e_book_query_to_string (q);
+	EBookQuery *nq = e_book_query_from_string (str);
+
+	g_free (str);
+	return nq;
+}
Index: addressbook/libebook-dbus/e-vcard.c
===================================================================
--- addressbook/libebook-dbus/e-vcard.c	(revision 409)
+++ addressbook/libebook-dbus/e-vcard.c	(working copy)
@@ -1 +1,2043 @@
-link ../libebook-orbit/./e-vcard.c
\ No newline at end of file
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* e-vcard.c
+ *
+ * Copyright (C) 2003 Ximian, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Chris Toshok (toshok@ximian.com)
+ */
+
+#include <glib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include "e-vcard.h"
+
+#define d(x)
+
+#define CRLF "\r\n"
+
+#define FOLD_LINES 0
+
+typedef enum {
+	EVC_ENCODING_RAW,    /* no encoding */
+	EVC_ENCODING_BASE64, /* base64 */
+	EVC_ENCODING_QP      /* quoted-printable */
+} EVCardEncoding;
+
+struct _EVCardPrivate {
+	GList *attributes;
+};
+
+struct _EVCardAttribute {
+	char  *group;
+	char  *name;
+	GList *params; /* EVCardParam */
+	GList *values;
+	GList *decoded_values;
+	EVCardEncoding encoding;
+	gboolean encoding_set;
+};
+
+struct _EVCardAttributeParam {
+	char     *name;
+	GList    *values;  /* GList of char*'s*/
+};
+
+static GObjectClass *parent_class;
+
+static void   _evc_base64_init(void);
+static size_t _evc_base64_encode_step(unsigned char *in, size_t len, gboolean break_lines, unsigned char *out, int *state, int *save);
+static size_t _evc_base64_decode_step(unsigned char *in, size_t len, unsigned char *out, int *state, unsigned int *save);
+static size_t _evc_base64_decode_simple (char *data, size_t len);
+static char  *_evc_base64_encode_simple (const char *data, size_t len);
+
+static void e_vcard_attribute_param_add_value_with_len (EVCardAttributeParam *param, const char *value, int length);
+
+static void
+e_vcard_dispose (GObject *object)
+{
+	EVCard *evc = E_VCARD (object);
+
+	if (evc->priv) {
+
+		g_list_foreach (evc->priv->attributes, (GFunc)e_vcard_attribute_free, NULL);
+		g_list_free (evc->priv->attributes);
+
+		g_free (evc->priv);
+		evc->priv = NULL;
+	}
+
+	if (G_OBJECT_CLASS (parent_class)->dispose)
+		G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+e_vcard_class_init (EVCardClass *klass)
+{
+	GObjectClass *object_class;
+
+	object_class = G_OBJECT_CLASS(klass);
+
+	parent_class = g_type_class_ref (G_TYPE_OBJECT);
+
+	object_class->dispose = e_vcard_dispose;
+
+	_evc_base64_init();
+}
+
+static void
+e_vcard_init (EVCard *evc)
+{
+	evc->priv = g_new0 (EVCardPrivate, 1);
+}
+
+GType
+e_vcard_get_type (void)
+{
+	static GType vcard_type = 0;
+
+	if (!vcard_type) {
+		static const GTypeInfo vcard_info =  {
+			sizeof (EVCardClass),
+			NULL,           /* base_init */
+			NULL,           /* base_finalize */
+			(GClassInitFunc) e_vcard_class_init,
+			NULL,           /* class_finalize */
+			NULL,           /* class_data */
+			sizeof (EVCard),
+			0,             /* n_preallocs */
+			(GInstanceInitFunc) e_vcard_init,
+		};
+
+		vcard_type = g_type_register_static (G_TYPE_OBJECT, "EVCard", &vcard_info, 0);
+	}
+
+	return vcard_type;
+}
+
+static void
+skip_to_next_line (const char **p)
+{
+	const char *lp = *p;
+
+	while (*lp != '\r' && *lp != '\n' && *lp != '\0')
+		lp++;
+
+	if (*lp == '\r') {
+		if (*++lp == '\n')
+			lp++;
+	}
+	
+	*p = lp;
+}
+
+/* skip forward until we hit a character in @s, CRLF, or \0.  leave *p
+   pointing at the character that causes us to stop */
+static void
+skip_until (const char **p, char *s)
+{
+	const char *lp;
+
+	lp = *p;
+
+	while (*lp != '\r' && *lp != '\0') {
+		gboolean s_matches = FALSE;
+		char *ls;
+		for (ls = s; *ls; ls = g_utf8_next_char (ls)) {
+			if (g_utf8_get_char (ls) == g_utf8_get_char (lp)) {
+				s_matches = TRUE;
+				break;
+			}
+		}
+
+		if (s_matches)
+			break;
+		lp = g_utf8_next_char (lp);
+	}
+
+	*p = lp;
+}
+
+#define WRITE_CHUNK \
+	if (chunk_start) { \
+		g_string_append_len (str, chunk_start, lp - chunk_start); \
+		chunk_start = NULL; \
+	}
+
+#define UNFOLD(p)				       \
+	else if (*p == '\r' || *p == '\n') {	       \
+		p++;				       \
+		if (*p == '\n' || *p == '\r') {	       \
+			p++;			       \
+			if (*p == ' ' || *p == '\t') { \
+				p++;		       \
+			} else {		       \
+				break;		       \
+			}			       \
+		} else if (*p == ' ' || *p == '\t') {  \
+			p++;			       \
+		} else {			       \
+			break;			       \
+		}				       \
+	}
+
+#define UNFOLD_CHUNK(p)				       \
+	else if (*p == '\r' || *p == '\n') {	       \
+		WRITE_CHUNK;			       \
+		p++;				       \
+		if (*p == '\n' || *p == '\r') {	       \
+			p++;			       \
+			if (*p == ' ' || *p == '\t') { \
+				p++;		       \
+			} else {		       \
+				break;		       \
+			}			       \
+		} else if (*p == ' ' || *p == '\t') {  \
+			p++;			       \
+		} else {			       \
+			break;			       \
+		}				       \
+	}
+
+
+static void
+read_attribute_value (EVCardAttribute *attr, const char **p, gboolean quoted_printable)
+{
+	const char *lp = *p;
+	GString *str;
+	const char *chunk_start = NULL;
+
+	/* read in the value */
+	str = g_string_sized_new (16);
+	while (*lp) {
+		//g_printerr("(%c)", *lp);
+		if (*lp == '=' && quoted_printable) {
+			char a, b;
+			WRITE_CHUNK;
+			if ((a = *(++lp)) == '\0') break;
+			if ((b = *(++lp)) == '\0') break;
+			else if (isxdigit(a) && isxdigit (b)) {
+				gunichar c;
+
+				a = tolower (a);
+				b = tolower (b);
+
+				c = (((a>='a'?a-'a'+10:a-'0')&0x0f) << 4)
+					| ((b>='a'?b-'a'+10:b-'0')&0x0f);
+				
+				g_string_insert_unichar (str, -1, c);
+			}
+			else 
+				{
+					g_string_append_c (str, a);
+					g_string_append_c (str, b);
+				}
+			
+			lp++;
+		}
+		else if (*lp == '\\') {
+			WRITE_CHUNK;
+			/* convert back to the non-escaped version of
+			   the characters */
+			lp = g_utf8_next_char(lp);
+			if (*lp == '\0') {
+				g_string_append_c (str, '\\');
+				break;
+			}
+			switch (*lp) {
+			case 'n': g_string_append_c (str, '\n'); break;
+			case 'r': g_string_append_c (str, '\r'); break;
+			case ';': g_string_append_c (str, ';'); break;
+			case ',': g_string_append_c (str, ','); break;
+			case '\\': g_string_append_c (str, '\\'); break;
+			default:
+				g_warning ("invalid escape, passing it through");
+				g_string_append_c (str, '\\');
+				g_string_insert_unichar (str, -1, g_utf8_get_char(lp));
+				break;
+			}
+			lp = g_utf8_next_char(lp);
+		}
+		else if ((*lp == ';') ||
+			 (*lp == ',' && !strcmp (attr->name, "CATEGORIES"))) {
+			WRITE_CHUNK;
+			e_vcard_attribute_add_value (attr, str->str);
+			g_string_assign (str, "");
+			lp = g_utf8_next_char(lp);
+		}
+		UNFOLD_CHUNK(lp)
+		else {
+			if (chunk_start == NULL)
+				chunk_start = lp;
+
+			//g_string_insert_unichar (str, -1, g_utf8_get_char (lp));
+			lp = g_utf8_next_char(lp);
+		}
+	}
+	WRITE_CHUNK;
+	if (str) {
+		e_vcard_attribute_add_value (attr, str->str);
+		g_string_free (str, TRUE);
+	}
+
+	*p = lp;
+}
+
+static void
+read_attribute_params (EVCardAttribute *attr, const char **p, gboolean *quoted_printable)
+{
+	const char *lp = *p;
+	GString *str;
+	EVCardAttributeParam *param = NULL;
+	gboolean in_quote = FALSE;
+	str = g_string_sized_new (16);
+	while (*lp != '\0') {
+		if (*lp == '"') {
+			in_quote = !in_quote;
+			lp = g_utf8_next_char (lp);
+		}
+		else if (in_quote || g_unichar_isalnum (g_utf8_get_char (lp)) || *lp == '-' || *lp == '_') {
+			g_string_insert_unichar (str, -1, g_utf8_get_char (lp));
+			lp = g_utf8_next_char (lp);
+		}
+		/* accumulate until we hit the '=' or ';'.  If we hit
+		 * a '=' the string contains the parameter name.  if
+		 * we hit a ';' the string contains the parameter
+		 * value and the name is either ENCODING (if value ==
+		 * QUOTED-PRINTABLE) or TYPE (in any other case.)
+		 */
+		else if (*lp == '=') {
+			if (str->len > 0) {
+				param = e_vcard_attribute_param_new (str->str);
+				g_string_assign (str, "");
+				lp = g_utf8_next_char (lp);
+			}
+			else {
+				skip_until (&lp, ":;");
+				if (*lp == '\r') {
+					lp = g_utf8_next_char (lp); /* \n */
+					lp = g_utf8_next_char (lp); /* start of the next line */
+					break;
+				}
+				else if (*lp == ';')
+					lp = g_utf8_next_char (lp);
+			}
+		}
+		else if (*lp == ';' || *lp == ':' || *lp == ',') {
+			gboolean colon = (*lp == ':');
+			gboolean comma = (*lp == ',');
+
+			if (param) {
+				if (str->len > 0) {
+					e_vcard_attribute_param_add_value_with_len (param, str->str, str->len);
+					g_string_assign (str, "");
+					if (!colon)
+						lp = g_utf8_next_char (lp);
+				}
+				else {
+					/* we've got a parameter of the form:
+					 * PARAM=(.*,)?[:;]
+					 * so what we do depends on if there are already values
+					 * for the parameter.  If there are, we just finish
+					 * this parameter and skip past the offending character
+					 * (unless it's the ':'). If there aren't values, we free
+					 * the parameter then skip past the character.
+					 */
+					if (!param->values) {
+						e_vcard_attribute_param_free (param);
+						param = NULL;
+						if (!colon)
+							lp = g_utf8_next_char (lp);
+					}
+				}
+
+				if (param
+				    && !strcmp (param->name, "ENCODING")
+				    && !g_ascii_strcasecmp (param->values->data, "quoted-printable")) {
+					*quoted_printable = TRUE;
+					e_vcard_attribute_param_free (param);
+					param = NULL;
+				}
+			}
+			else {
+				if (str->len > 0) {
+					char *param_name;
+					if (!g_ascii_strcasecmp (str->str,
+								 "quoted-printable")) {
+						param_name = "ENCODING";
+						*quoted_printable = TRUE;
+					}
+					/* apple's broken addressbook app outputs naked BASE64
+					   parameters, which aren't even vcard 3.0 compliant. */
+					else if (!g_ascii_strcasecmp (str->str,
+								      "base64")) {
+						param_name = "ENCODING";
+						g_string_assign (str, "b");
+					}
+					else {
+						param_name = "TYPE";
+					}
+
+					if (param_name) {
+						param = e_vcard_attribute_param_new (param_name);
+						e_vcard_attribute_param_add_value_with_len (param, str->str, str->len);
+					}
+					g_string_assign (str, "");
+					if (!colon)
+						lp = g_utf8_next_char (lp);
+				}
+				else {
+					/* we've got an attribute with a truly empty
+					   attribute parameter.  So it's of the form:
+					   
+					   ATTR;[PARAM=value;]*;[PARAM=value;]*:
+
+					   (note the extra ';')
+
+					   the only thing to do here is, well.. nothing.
+					   we skip over the character if it's not a colon,
+					   and the rest is handled for us: We'll either
+					   continue through the loop again if we hit a ';',
+					   or we'll break out correct below if it was a ':' */
+					if (!colon)
+						lp = g_utf8_next_char (lp);
+				}
+			}
+			if (param && !comma) {
+				e_vcard_attribute_add_param (attr, param);
+				param = NULL;
+			}
+			if (colon)
+				break;
+		}
+		UNFOLD (lp)
+		else {
+			g_warning ("invalid character found in parameter spec");
+			g_string_assign (str, "");
+			skip_until (&lp, ":;");
+			
+			if (*lp == '\r') {
+				lp = g_utf8_next_char (lp); /* \n */
+				lp = g_utf8_next_char (lp); /* start of the next line */
+				break;
+			}
+		}
+	}
+
+	if (str)
+		g_string_free (str, TRUE);
+
+	attr->params = g_list_reverse (attr->params);
+	
+	*p = lp;
+}
+
+/* reads an entire attribute from the input buffer, leaving p pointing
+   at the start of the next line (past the \r\n) */
+static EVCardAttribute*
+read_attribute (const char **p)
+{
+	char *attr_group = NULL;
+	char *attr_name = NULL;
+	EVCardAttribute *attr = NULL;
+	GString *str;
+	const char *lp = *p;
+	char c;
+	gboolean is_qp = FALSE;
+	gunichar uc;
+
+	/* first read in the group/name */
+	str = g_string_sized_new (16);
+	while ((c = *lp)) {
+		if (c == ':' || c == ';') {
+			if (str->len != 0) {
+				/* we've got a name, break out to the value/attribute parsing */
+				attr_name = g_string_free (str, FALSE);
+				break;
+			}
+			else {
+				/* a line of the form:
+				 * (group.)?[:;]
+				 *
+				 * since we don't have an attribute
+				 * name, skip to the end of the line
+				 * and try again.
+				 */
+				g_string_free (str, TRUE);
+				*p = lp;
+				skip_to_next_line(p);
+				goto lose;
+			}
+		}
+		else if (c == '.') {
+			if (attr_group) {
+				g_warning ("extra `.' in attribute specification.  ignoring extra group `%s'",
+					   str->str);
+				g_string_assign (str, "");
+			}
+			if (str->len != 0) {
+				attr_group = g_string_free (str, FALSE);
+				g_string_assign (str, "");
+			}
+		}
+		else if (g_unichar_isalnum ((uc = g_utf8_get_char (lp))) ||c == '-' || c == '_') {
+			g_string_insert_unichar (str, -1, g_unichar_toupper (uc));
+		}
+		UNFOLD (lp)
+		else {
+			g_warning ("invalid character '%c' found in attribute group/name", c);
+			g_string_free (str, TRUE);
+			*p = lp;
+			skip_to_next_line(p);
+			goto lose;
+		}
+
+		lp = g_utf8_next_char(lp);
+	}
+
+	if (!attr_name) {
+		goto lose;
+	}
+
+	attr = e_vcard_attribute_new (attr_group, attr_name);
+	g_free (attr_group);
+	g_free (attr_name);
+
+	if (*lp == ';') {
+		/* skip past the ';' */
+		lp = g_utf8_next_char(lp);
+		read_attribute_params (attr, &lp, &is_qp);
+		if (is_qp)
+			attr->encoding = EVC_ENCODING_RAW;
+	}
+	if (*lp == ':') {
+		/* skip past the ':' */
+		lp = g_utf8_next_char(lp);
+		read_attribute_value (attr, &lp, is_qp);
+	}
+
+	*p = lp;
+
+	if (!attr->values) {
+		goto lose;
+	}
+
+	return attr;
+ lose:
+	if (attr)
+		e_vcard_attribute_free (attr);
+	return NULL;
+}
+
+static void
+parse (EVCard *evc, const char *str)
+{
+	EVCardAttribute *attr;
+
+	if (str == NULL)
+		return;
+	
+	attr = read_attribute (&str);
+	if (!attr || attr->group || strcmp (attr->name, "BEGIN")) {
+		g_warning ("vcard began without a BEGIN:VCARD\n");
+	}
+	if (attr && !strcmp (attr->name, "BEGIN"))
+		e_vcard_attribute_free (attr);
+	else if (attr)
+		e_vcard_add_attribute (evc, attr);
+	
+	while ((attr = read_attribute (&str))) {
+		if (strcmp (attr->name, "END") != 0)
+			e_vcard_add_attribute (evc, attr);
+		else
+			break;
+	}
+
+	if (!attr || attr->group || strcmp (attr->name, "END"))
+		g_warning ("vcard ended without END:VCARD\n");
+	
+	if (attr && !strcmp (attr->name, "END"))
+		e_vcard_attribute_free (attr);
+
+	evc->priv->attributes = g_list_reverse (evc->priv->attributes);
+}
+
+/**
+ * e_vcard_escape_string:
+ * @s: the string to escape
+ *
+ * Escapes a string according to RFC2426, section 5.
+ *
+ * Return value: A newly allocated, escaped string.
+ **/
+char*
+e_vcard_escape_string (const char *s)
+{
+	GString *str;
+	const char *p;
+
+	str = g_string_new ("");
+
+	/* Escape a string as described in RFC2426, section 5 */
+	for (p = s; p && *p; p++) {
+		switch (*p) {
+		case '\n':
+			g_string_append (str, "\\n");
+			break;
+		case '\r':
+			if (*(p+1) == '\n')
+				p++;
+			g_string_append (str, "\\n");
+			break;
+		case ';':
+			g_string_append (str, "\\;");
+			break;
+		case ',':
+			g_string_append (str, "\\,");
+			break;
+		case '\\':
+			g_string_append (str, "\\\\");
+			break;
+		default:
+			g_string_append_c (str, *p);
+			break;
+		}
+	}
+
+	return g_string_free (str, FALSE);
+}
+
+/**
+ * e_vcard_unescape_string:
+ * @s: the string to unescape
+ *
+ * Unescapes a string according to RFC2426, section 5.
+ *
+ * Return value: A newly allocated, unescaped string.
+ **/
+char*
+e_vcard_unescape_string (const char *s)
+{
+	GString *str;
+	const char *p;
+
+	g_return_val_if_fail (s != NULL, NULL);
+
+	str = g_string_new ("");
+
+	/* Unescape a string as described in RFC2426, section 5 */
+	for (p = s; *p; p++) {
+		if (*p == '\\') {
+			p++;
+			if (*p == '\0') {
+				g_string_append_c (str, '\\');
+				break;
+			}
+			switch (*p) {
+			case 'n':  g_string_append_c (str, '\n'); break;
+			case 'r':  g_string_append_c (str, '\r'); break;
+			case ';':  g_string_append_c (str, ';'); break;
+			case ',':  g_string_append_c (str, ','); break;
+			case '\\': g_string_append_c (str, '\\'); break;
+			default:
+				g_warning ("invalid escape, passing it through");
+				g_string_append_c (str, '\\');
+				g_string_append_unichar (str, g_utf8_get_char(p));
+				break;
+			}
+		}
+	}
+
+	return g_string_free (str, FALSE);
+}
+
+void
+e_vcard_construct (EVCard *evc, const char *str)
+{
+	g_return_if_fail (E_IS_VCARD (evc));
+	g_return_if_fail (str != NULL);
+
+	if (*str)
+		parse (evc, str);
+}
+
+/**
+ * e_vcard_new:
+ * 
+ * Creates a new, blank #EVCard.
+ *
+ * Return value: A new, blank #EVCard.
+ **/
+EVCard *
+e_vcard_new ()
+{
+	return e_vcard_new_from_string ("");
+}
+
+/**
+ * e_vcard_new_from_string:
+ * @str: a string representation of the vcard to create
+ *
+ * Creates a new #EVCard from the passed-in string
+ * representation.
+ *
+ * Return value: A new #EVCard.
+ **/
+EVCard *
+e_vcard_new_from_string (const char *str)
+{
+	EVCard *evc;
+
+	g_return_val_if_fail (str != NULL, NULL);
+
+	evc = g_object_new (E_TYPE_VCARD, NULL);
+
+	e_vcard_construct (evc, str);
+
+	return evc;
+}
+
+static char*
+e_vcard_to_string_vcard_21  (EVCard *evc)
+{
+	g_warning ("need to implement e_vcard_to_string_vcard_21");
+	return g_strdup ("");
+}
+
+static char*
+e_vcard_to_string_vcard_30 (EVCard *evc)
+{
+	GList *l;
+	GList *v;
+
+	GString *str;
+
+	str = g_string_new ("BEGIN:VCARD" CRLF);
+
+	/* we hardcode the version (since we're outputting to a
+	   specific version) and ignore any version attributes the
+	   vcard might contain */
+	g_string_append (str, "VERSION:3.0" CRLF);
+
+	for (l = evc->priv->attributes; l; l = l->next) {
+		GList *p;
+		EVCardAttribute *attr = l->data;
+		GString *attr_str;
+
+		if (!strcmp (attr->name, "VERSION"))
+			continue;
+
+		attr_str = g_string_new ("");
+
+		/* From rfc2425, 5.8.2
+		 *
+		 * contentline  = [group "."] name *(";" param) ":" value CRLF
+		 */
+
+		if (attr->group) {
+			g_string_append (attr_str, attr->group);
+			g_string_append_c (attr_str, '.');
+		}
+		g_string_append (attr_str, attr->name);
+
+		/* handle the parameters */
+		for (p = attr->params; p; p = p->next) {
+			EVCardAttributeParam *param = p->data;
+			/* 5.8.2:
+			 * param        = param-name "=" param-value *("," param-value)
+			 */
+			g_string_append_c (attr_str, ';');
+			g_string_append (attr_str, param->name);
+			if (param->values) {
+				g_string_append_c (attr_str, '=');
+				for (v = param->values; v; v = v->next) {
+					char *value = v->data;
+					char *p = value;
+					gboolean quotes = FALSE;
+					while (*p) {
+						if (!g_unichar_isalnum (g_utf8_get_char (p))) {
+							quotes = TRUE;
+							break;
+						}
+						p = g_utf8_next_char (p);
+					}
+					if (quotes)
+						g_string_append_c (attr_str, '"');
+					g_string_append (attr_str, value);
+					if (quotes)
+						g_string_append_c (attr_str, '"');
+					if (v->next)
+						g_string_append_c (attr_str, ',');
+				}
+			}
+		}
+
+		g_string_append_c (attr_str, ':');
+
+		for (v = attr->values; v; v = v->next) {
+			char *value = v->data;
+			char *escaped_value = NULL;
+
+			escaped_value = e_vcard_escape_string (value);
+
+			g_string_append (attr_str, escaped_value);
+			if (v->next) {
+				/* XXX toshok - i hate you, rfc 2426.
+				   why doesn't CATEGORIES use a ; like
+				   a normal list attribute? */
+				if (!strcmp (attr->name, "CATEGORIES"))
+					g_string_append_c (attr_str, ',');
+				else
+					g_string_append_c (attr_str, ';');
+			}
+
+			g_free (escaped_value);
+		}
+
+		/* 5.8.2:
+		 * When generating a content line, lines longer than 75
+		 * characters SHOULD be folded
+		 */
+#if FOLD_LINES
+		l = 0;
+		do {
+			
+			if ((g_utf8_strlen (attr_str->str, -1) -l) > 75) {
+				char *p;
+
+				l += 75;
+				p = g_utf8_offset_to_pointer (attr_str->str, l);
+			
+				g_string_insert_len (attr_str, (p - attr_str->str), CRLF " ", sizeof (CRLF " ") - 1);
+			}
+			else
+				break;
+		} while (l < g_utf8_strlen (attr_str->str, -1));
+#endif
+
+		g_string_append (attr_str, CRLF);
+
+		g_string_append (str, attr_str->str);
+		g_string_free (attr_str, TRUE);
+	}
+
+	g_string_append (str, "END:VCARD");
+
+	return g_string_free (str, FALSE);
+}
+
+/**
+ * e_vcard_to_string:
+ * @evc: the #EVCard to export
+ * @format: the format to export to
+ *
+ * Exports @evc to a string representation, specified
+ * by the @format argument.
+ *
+ * Return value: A newly allocated string representing the vcard.
+ **/
+char*
+e_vcard_to_string (EVCard *evc, EVCardFormat format)
+{
+	g_return_val_if_fail (E_IS_VCARD (evc), NULL);
+
+	switch (format) {
+	case EVC_FORMAT_VCARD_21:
+		return e_vcard_to_string_vcard_21 (evc);
+	case EVC_FORMAT_VCARD_30:
+		return e_vcard_to_string_vcard_30 (evc);
+	default:
+		g_warning ("invalid format specifier passed to e_vcard_to_string");
+		return g_strdup ("");
+	}
+}
+
+/**
+ * e_vcard_dump_structure:
+ * @evc: the #EVCard to dump
+ *
+ * Prints a dump of @evc's structure to stdout. Used for
+ * debugging.
+ **/
+void
+e_vcard_dump_structure (EVCard *evc)
+{
+	GList *a;
+	GList *v;
+	int i;
+
+	g_return_if_fail (E_IS_VCARD (evc));
+
+	printf ("vCard\n");
+	for (a = evc->priv->attributes; a; a = a->next) {
+		GList *p;
+		EVCardAttribute *attr = a->data;
+		printf ("+-- %s\n", attr->name);
+		if (attr->params) {
+			printf ("    +- params=\n");
+
+			for (p = attr->params, i = 0; p; p = p->next, i++) {
+				EVCardAttributeParam *param = p->data;
+				printf ("    |   [%d] = %s", i,param->name);
+				printf ("(");
+				for (v = param->values; v; v = v->next) {
+					char *value = e_vcard_escape_string ((char*)v->data);
+					printf ("%s", value);
+					if (v->next)
+						printf (",");
+					g_free (value);
+				}
+
+				printf (")\n");
+			}
+		}
+		printf ("    +- values=\n");
+		for (v = attr->values, i = 0; v; v = v->next, i++) {
+			printf ("        [%d] = `%s'\n", i, (char*)v->data);
+		}
+	}
+}
+
+/**
+ * e_vcard_attribute_new:
+ * @attr_group: a group name
+ * @attr_name: an attribute name
+ *
+ * Creates a new #EVCardAttribute with the specified group and
+ * attribute names.
+ *
+ * Return value: A new #EVCardAttribute.
+ **/
+EVCardAttribute*
+e_vcard_attribute_new (const char *attr_group, const char *attr_name)
+{
+	EVCardAttribute *attr;
+
+	attr = g_slice_new0 (EVCardAttribute);
+
+	attr->group = g_strdup (attr_group);
+	attr->name = g_ascii_strup (attr_name, -1);
+
+	return attr;
+}
+
+/**
+ * e_vcard_attribute_free:
+ * @attr: attribute to free
+ *
+ * Frees an attribute, its values and its parameters.
+ **/
+void
+e_vcard_attribute_free (EVCardAttribute *attr)
+{
+	g_return_if_fail (attr != NULL);
+
+	g_free (attr->group);
+	g_free (attr->name);
+
+	e_vcard_attribute_remove_values (attr);
+
+	e_vcard_attribute_remove_params (attr);
+
+	g_slice_free (EVCardAttribute, attr);
+}
+
+/**
+ * e_vcard_attribute_copy:
+ * @attr: attribute to copy
+ *
+ * Makes a copy of @attr.
+ *
+ * Return value: A new #EVCardAttribute identical to @attr.
+ **/
+EVCardAttribute*
+e_vcard_attribute_copy (EVCardAttribute *attr)
+{
+	EVCardAttribute *a;
+	GList *p;
+
+	g_return_val_if_fail (attr != NULL, NULL);
+
+	a = e_vcard_attribute_new (e_vcard_attribute_get_group (attr),
+				   e_vcard_attribute_get_name (attr));
+
+	for (p = attr->values; p; p = p->next)
+		e_vcard_attribute_add_value (a, p->data);
+	for (p = attr->params; p; p = p->next)
+		e_vcard_attribute_add_param (a, e_vcard_attribute_param_copy (p->data));
+
+	return a;
+}
+
+/**
+ * e_vcard_remove_attributes:
+ * @evc: vcard object
+ * @attr_group: group name of attributes to be removed
+ * @attr_name: name of the arributes to be removed
+ *
+ * Removes all the attributes with group name and attribute name equal to 
+ * passed in values. If @attr_group is %NULL or an empty string,
+ * it removes all the attributes with passed in name irrespective of
+ * their group names.
+ **/
+void
+e_vcard_remove_attributes (EVCard *evc, const char *attr_group, const char *attr_name)
+{
+	GList *attr;
+
+	g_return_if_fail (E_IS_VCARD (evc));
+	g_return_if_fail (attr_name != NULL);
+
+	attr = evc->priv->attributes;
+	while (attr) {
+		GList *next_attr;
+		EVCardAttribute *a = attr->data;
+
+		next_attr = attr->next;
+
+		if (((!attr_group || *attr_group == '\0') ||
+		     (attr_group && !g_ascii_strcasecmp (attr_group, a->group))) &&
+		    ((!attr_name && !a->name) || !g_ascii_strcasecmp (attr_name, a->name))) {
+
+			/* matches, remove/delete the attribute */
+			evc->priv->attributes = g_list_delete_link (evc->priv->attributes, attr);
+
+			e_vcard_attribute_free (a);
+		}
+
+		attr = next_attr;
+	}
+}
+
+/**
+ * e_vcard_remove_attribute:
+ * @evc: an #EVCard
+ * @attr: an #EVCardAttribute to remove
+ *
+ * Removes @attr from @evc and frees it.
+ **/
+void
+e_vcard_remove_attribute (EVCard *evc, EVCardAttribute *attr)
+{
+	g_return_if_fail (E_IS_VCARD (evc));
+	g_return_if_fail (attr != NULL);
+
+	evc->priv->attributes = g_list_remove (evc->priv->attributes, attr);
+	e_vcard_attribute_free (attr);
+}
+
+/**
+ * e_vcard_add_attribute:
+ * @evc: an #EVCard
+ * @attr: an #EVCardAttribute to add
+ *
+ * Adds @attr to @evc.
+ **/
+void
+e_vcard_add_attribute (EVCard *evc, EVCardAttribute *attr)
+{
+	g_return_if_fail (E_IS_VCARD (evc));
+	g_return_if_fail (attr != NULL);
+
+	evc->priv->attributes = g_list_prepend (evc->priv->attributes, attr);
+}
+
+/**
+ * e_vcard_add_attribute_with_value:
+ * @evcard: an #EVCard
+ * @attr: an #EVCardAttribute to add
+ * @value: a value to assign to the attribute
+ *
+ * Adds @attr to @evcard, setting it to @value.
+ **/
+void
+e_vcard_add_attribute_with_value (EVCard *evcard,
+				  EVCardAttribute *attr, const char *value)
+{
+	g_return_if_fail (E_IS_VCARD (evcard));
+	g_return_if_fail (attr != NULL);
+
+	e_vcard_attribute_add_value (attr, value);
+
+	e_vcard_add_attribute (evcard, attr);
+}
+
+/**
+ * e_vcard_add_attribute_with_values:
+ * @evcard: an @EVCard
+ * @attr: an #EVCardAttribute to add
+ * @...: a %NULL-terminated list of values to assign to the attribute
+ *
+ * Adds @attr to @evcard, assigning the list of values to it.
+ **/
+void
+e_vcard_add_attribute_with_values (EVCard *evcard, EVCardAttribute *attr, ...)
+{
+	va_list ap;
+	char *v;
+
+	g_return_if_fail (E_IS_VCARD (evcard));
+	g_return_if_fail (attr != NULL);
+
+	va_start (ap, attr);
+
+	while ((v = va_arg (ap, char*))) {
+		e_vcard_attribute_add_value (attr, v);
+	}
+
+	va_end (ap);
+
+	e_vcard_add_attribute (evcard, attr);
+}
+
+/**
+ * e_vcard_attribute_add_value:
+ * @attr: an #EVCardAttribute
+ * @value: a string value
+ *
+ * Adds @value to @attr's list of values.
+ **/
+void
+e_vcard_attribute_add_value (EVCardAttribute *attr, const char *value)
+{
+	g_return_if_fail (attr != NULL);
+
+	attr->values = g_list_append (attr->values, g_strdup (value));
+}
+
+/**
+ * e_vcard_attribute_add_value_decoded:
+ * @attr: an #EVCardAttribute
+ * @value: an encoded value
+ * @len: the length of the encoded value, in bytes
+ *
+ * Decodes @value according to the encoding used for @attr, and
+ * adds it to @attr's list of values.
+ **/
+void
+e_vcard_attribute_add_value_decoded (EVCardAttribute *attr, const char *value, int len)
+{
+	g_return_if_fail (attr != NULL);
+
+	switch (attr->encoding) {
+	case EVC_ENCODING_RAW:
+		g_warning ("can't add_value_decoded with an attribute using RAW encoding.  you must set the ENCODING parameter first");
+		break;
+	case EVC_ENCODING_BASE64: {
+		char *b64_data = _evc_base64_encode_simple (value, len);
+		GString *decoded = g_string_new_len (value, len);
+
+		/* make sure the decoded list is up to date */
+		e_vcard_attribute_get_values_decoded (attr);
+
+		d(printf ("base64 encoded value: %s\n", b64_data));
+		d(printf ("original length: %d\n", len));
+
+		attr->values = g_list_append (attr->values, b64_data);
+		attr->decoded_values = g_list_append (attr->decoded_values, decoded);
+		break;
+	}
+	case EVC_ENCODING_QP:
+		g_warning ("need to implement quoted printable decoding");
+		break;
+	}
+}
+
+/**
+ * e_vcard_attribute_add_values:
+ * @attr: an #EVCardAttribute
+ * ...: a %NULL-terminated list of strings
+ *
+ * Adds a list of values to @attr.
+ **/
+void
+e_vcard_attribute_add_values (EVCardAttribute *attr,
+			      ...)
+{
+	va_list ap;
+	char *v;
+
+	g_return_if_fail (attr != NULL);
+
+	va_start (ap, attr);
+
+	while ((v = va_arg (ap, char*))) {
+		e_vcard_attribute_add_value (attr, v);
+	}
+
+	va_end (ap);
+}
+
+static void
+free_gstring (GString *str)
+{
+	g_string_free (str, TRUE);
+}
+
+/**
+ * e_vcard_attribute_remove_values:
+ * @attr: an #EVCardAttribute
+ *
+ * Removes all values from @attr.
+ **/
+void
+e_vcard_attribute_remove_values (EVCardAttribute *attr)
+{
+	g_return_if_fail (attr != NULL);
+
+	g_list_foreach (attr->values, (GFunc)g_free, NULL);
+	g_list_free (attr->values);
+	attr->values = NULL;
+
+	g_list_foreach (attr->decoded_values, (GFunc)free_gstring, NULL);
+	g_list_free (attr->decoded_values);
+	attr->decoded_values = NULL;
+}
+
+void
+e_vcard_attribute_remove_value (EVCardAttribute *attr, const char *s)
+{
+	GList *l;
+
+	g_return_if_fail (attr != NULL);
+	g_return_if_fail (s != NULL);
+
+	l = g_list_find_custom (attr->values, s, (GCompareFunc)strcmp);
+	if (l == NULL) {
+		return;
+	}
+	
+	attr->values = g_list_delete_link (attr->values, l);
+}
+
+/**
+ * e_vcard_attribute_remove_params:
+ * @attr: an #EVCardAttribute
+ *
+ * Removes all parameters from @attr.
+ **/
+void
+e_vcard_attribute_remove_params (EVCardAttribute *attr)
+{
+	g_return_if_fail (attr != NULL);
+
+	g_list_foreach (attr->params, (GFunc)e_vcard_attribute_param_free, NULL);
+	g_list_free (attr->params);
+	attr->params = NULL;
+
+	/* also remove the cached encoding on this attribute */
+	attr->encoding_set = FALSE;
+	attr->encoding = EVC_ENCODING_RAW;
+}
+
+/**
+ * e_vcard_attribute_param_new:
+ * @name: the name of the new parameter
+ *
+ * Creates a new parameter named @name.
+ *
+ * Return value: A new #EVCardAttributeParam.
+ **/
+EVCardAttributeParam*
+e_vcard_attribute_param_new (const char *name)
+{
+	EVCardAttributeParam *param = g_slice_new (EVCardAttributeParam);
+	param->values = NULL;
+	param->name = g_ascii_strup (name, -1);
+
+	return param;
+}
+
+/**
+ * e_vcard_attribute_param_free:
+ * @param: an #EVCardAttributeParam
+ *
+ * Frees @param and its values.
+ **/
+void
+e_vcard_attribute_param_free (EVCardAttributeParam *param)
+{
+	g_return_if_fail (param != NULL);
+
+	g_free (param->name);
+
+	e_vcard_attribute_param_remove_values (param);
+
+	g_slice_free (EVCardAttributeParam, param);
+}
+
+/**
+ * e_vcard_attribute_param_copy:
+ * @param: an #EVCardAttributeParam
+ *
+ * Makes a copy of @param.
+ *
+ * Return value: a new #EVCardAttributeParam identical to @param.
+ **/
+EVCardAttributeParam*
+e_vcard_attribute_param_copy (EVCardAttributeParam *param)
+{
+	EVCardAttributeParam *p;
+	GList *l;
+
+	g_return_val_if_fail (param != NULL, NULL);
+
+	p = e_vcard_attribute_param_new (e_vcard_attribute_param_get_name (param));
+
+	for (l = param->values; l; l = l->next) {
+		e_vcard_attribute_param_add_value (p, l->data);
+	}
+
+	return p;
+}
+
+/**
+ * e_vcard_attribute_add_param:
+ * @attr: an #EVCardAttribute
+ * @param: an #EVCardAttributeParam to add
+ *
+ * Adds @param to @attr's list of parameters.
+ **/
+void
+e_vcard_attribute_add_param (EVCardAttribute *attr,
+			     EVCardAttributeParam *param)
+{
+	g_return_if_fail (attr != NULL);
+	g_return_if_fail (param != NULL);
+
+	attr->params = g_list_prepend (attr->params, param);
+
+	/* we handle our special encoding stuff here */
+
+	if (!strcmp (param->name, EVC_ENCODING)) {
+		if (attr->encoding_set) {
+			g_warning ("ENCODING specified twice");
+			return;
+		}
+
+		if (param->values && param->values->data) {
+			if (!g_ascii_strcasecmp ((char*)param->values->data, "b") ||
+			    !g_ascii_strcasecmp ((char*)param->values->data, "BASE64"))
+				attr->encoding = EVC_ENCODING_BASE64;
+			else if (!g_ascii_strcasecmp ((char*)param->values->data, EVC_QUOTEDPRINTABLE))
+				attr->encoding = EVC_ENCODING_QP;
+			else {
+				g_warning ("Unknown value `%s' for ENCODING parameter.  values will be treated as raw",
+					   (char*)param->values->data);
+			}
+
+			attr->encoding_set = TRUE;
+		}
+		else {
+			g_warning ("ENCODING parameter added with no value");
+		}
+	}
+}
+
+/**
+ * e_vcard_attribute_param_add_value:
+ * @param: an #EVCardAttributeParam
+ * @value: a string value to add
+ *
+ * Adds @value to @param's list of values.
+ **/
+void
+e_vcard_attribute_param_add_value (EVCardAttributeParam *param,
+				   const char *value)
+{
+	g_return_if_fail (param != NULL);
+
+	param->values = g_list_append (param->values, g_strdup (value));
+}
+
+/**
+ * e_vcard_attribute_param_add_value_with_len:
+ * @param: an #EVCardAttributeParam
+ * @value: a string value to add
+ * @length: the length of @string
+ *
+ * Adds @value to @param's list of values.  This function is for internal use
+ * only.
+ **/
+static void
+e_vcard_attribute_param_add_value_with_len (EVCardAttributeParam *param,
+					    const char *value,
+					    int length)
+{
+	g_return_if_fail (param != NULL);
+
+	param->values = g_list_append (param->values, g_strndup (value, length));
+}
+
+/**
+ * e_vcard_attribute_param_add_values:
+ * @param: an #EVCardAttributeParam
+ * ...: a %NULL-terminated list of strings
+ *
+ * Adds a list of values to @param.
+ **/
+void
+e_vcard_attribute_param_add_values (EVCardAttributeParam *param,
+				    ...)
+{
+	va_list ap;
+	char *v;
+
+	g_return_if_fail (param != NULL);
+
+	va_start (ap, param);
+
+	while ((v = va_arg (ap, char*))) {
+		e_vcard_attribute_param_add_value (param, v);
+	}
+
+	va_end (ap);
+}
+
+/**
+ * e_vcard_attribute_add_param_with_value:
+ * @attr: an #EVCardAttribute
+ * @param: an #EVCardAttributeParam
+ * @value: a string value
+ *
+ * Adds @value to @param, then adds @param to @attr.
+ **/
+void
+e_vcard_attribute_add_param_with_value (EVCardAttribute *attr,
+					EVCardAttributeParam *param, const char *value)
+{
+	g_return_if_fail (attr != NULL);
+	g_return_if_fail (param != NULL);
+
+	e_vcard_attribute_param_add_value (param, value);
+
+	e_vcard_attribute_add_param (attr, param);
+}
+
+/**
+ * e_vcard_attribute_add_param_with_values:
+ * @attr: an #EVCardAttribute
+ * @param: an #EVCardAttributeParam
+ * ...: a %NULL-terminated list of strings
+ *
+ * Adds the list of values to @param, then adds @param
+ * to @attr.
+ **/
+void
+e_vcard_attribute_add_param_with_values (EVCardAttribute *attr,
+					 EVCardAttributeParam *param, ...)
+{
+	va_list ap;
+	char *v;
+
+	g_return_if_fail (attr != NULL);
+	g_return_if_fail (param != NULL);
+
+	va_start (ap, param);
+
+	while ((v = va_arg (ap, char*))) {
+		e_vcard_attribute_param_add_value (param, v);
+	}
+
+	va_end (ap);
+
+	e_vcard_attribute_add_param (attr, param);
+}
+
+/**
+ * e_vcard_attribute_param_remove_values:
+ * @param: an #EVCardAttributeParam
+ *
+ * Removes and frees all values from @param.
+ **/
+void
+e_vcard_attribute_param_remove_values (EVCardAttributeParam *param)
+{
+	g_return_if_fail (param != NULL);
+
+	g_list_foreach (param->values, (GFunc)g_free, NULL);
+	g_list_free (param->values);
+	param->values = NULL;
+}
+
+void
+e_vcard_attribute_remove_param_value (EVCardAttribute *attr, const char *param_name, const char *s)
+{
+	GList *l, *params;
+	EVCardAttributeParam *param;
+
+	g_return_if_fail (attr != NULL);
+	g_return_if_fail (param_name != NULL);
+	g_return_if_fail (s != NULL);
+
+	params = e_vcard_attribute_get_params (attr);
+
+	for (l = params; l; l = l->next) {
+		param = l->data;
+		if (g_ascii_strcasecmp (e_vcard_attribute_param_get_name (param), param_name) == 0) {
+			goto found;
+		}
+	}
+	return;
+
+ found:
+	l = g_list_find_custom (param->values, s, (GCompareFunc)strcmp);
+	if (l == NULL) {
+		return;
+	}
+	
+	param->values = g_list_delete_link (param->values, l);
+	if (param->values == NULL) {
+		e_vcard_attribute_param_free (param);
+		attr->params = g_list_remove (attr->params, param);
+	}
+}
+
+/**
+ * e_vcard_get_attributes:
+ * @evcard: an #EVCard
+ *
+ * Gets the list of attributes from @evcard. The list and its
+ * contents are owned by @evcard, and must not be freed.
+ *
+ * Return value: A list of attributes of type #EVCardAttribute.
+ **/
+GList*
+e_vcard_get_attributes (EVCard *evcard)
+{
+	g_return_val_if_fail (E_IS_VCARD (evcard), NULL);
+
+	return evcard->priv->attributes;
+}
+
+EVCardAttribute *
+e_vcard_get_attribute (EVCard     *vcard,
+		       const char *name)
+{
+        GList *attrs, *l;
+
+        g_return_val_if_fail (E_IS_VCARD (vcard), NULL);
+        g_return_val_if_fail (name != NULL, NULL);
+
+        attrs = e_vcard_get_attributes (vcard);
+        for (l = attrs; l; l = l->next) {
+                EVCardAttribute *attr;
+		
+                attr = (EVCardAttribute *) l->data;
+                if (strcmp (attr->name, name) == 0)
+                        return attr;
+        }
+
+        return NULL;
+}
+/**
+ * e_vcard_attribute_get_group:
+ * @attr: an #EVCardAttribute
+ *
+ * Gets the group name of @attr.
+ *
+ * Return value: The attribute's group name.
+ **/
+const char*
+e_vcard_attribute_get_group (EVCardAttribute *attr)
+{
+	g_return_val_if_fail (attr != NULL, NULL);
+
+	return attr->group;
+}
+
+/**
+ * e_vcard_attribute_get_name:
+ * @attr: an #EVCardAttribute
+ *
+ * Gets the name of @attr.
+ *
+ * Return value: The attribute's name.
+ **/
+const char*
+e_vcard_attribute_get_name (EVCardAttribute *attr)
+{
+	g_return_val_if_fail (attr != NULL, NULL);
+
+	return attr->name;
+}
+
+/**
+ * e_vcard_attribute_get_values:
+ * @attr: an #EVCardAttribute
+ *
+ * Gets the list of values from @attr. The list and its
+ * contents are owned by @attr, and must not be freed.
+ * 
+ * Return value: A list of string values.
+ **/
+GList*
+e_vcard_attribute_get_values (EVCardAttribute *attr)
+{
+	g_return_val_if_fail (attr != NULL, NULL);
+
+	return attr->values;
+}
+
+/**
+ * e_vcard_attribute_get_values_decoded:
+ * @attr: an #EVCardAttribute
+ *
+ * Gets the list of values from @attr, decoding them if
+ * necessary. The list and its contents are owned by @attr,
+ * and must not be freed.
+ *
+ * Return value: A list of values of type #GString.
+ **/
+GList*
+e_vcard_attribute_get_values_decoded (EVCardAttribute *attr)
+{
+	g_return_val_if_fail (attr != NULL, NULL);
+
+	if (!attr->decoded_values) {
+		GList *l;
+		switch (attr->encoding) {
+		case EVC_ENCODING_RAW:
+			for (l = attr->values; l; l = l->next)
+				attr->decoded_values = g_list_prepend (attr->decoded_values, g_string_new ((char*)l->data));
+			attr->decoded_values = g_list_reverse (attr->decoded_values);
+			break;
+		case EVC_ENCODING_BASE64:
+			for (l = attr->values; l; l = l->next) {
+				char *decoded = g_strdup ((char*)l->data);
+				int len = _evc_base64_decode_simple (decoded, strlen (decoded));
+				attr->decoded_values = g_list_prepend (attr->decoded_values, g_string_new_len (decoded, len));
+				g_free (decoded);
+			}
+			attr->decoded_values = g_list_reverse (attr->decoded_values);
+			break;
+		case EVC_ENCODING_QP:
+			g_warning ("need to implement quoted printable decoding");
+			break;
+		}
+	}
+
+	return attr->decoded_values;
+}
+
+/**
+ * e_vcard_attribute_is_single_valued:
+ * @attr: an #EVCardAttribute
+ *
+ * Checks if @attr has a single value.
+ *
+ * Return value: %TRUE if the attribute has exactly one value, %FALSE otherwise.
+ **/
+gboolean
+e_vcard_attribute_is_single_valued (EVCardAttribute *attr)
+{
+	g_return_val_if_fail (attr != NULL, FALSE);
+
+	if (attr->values == NULL
+	    || attr->values->next != NULL)
+		return FALSE;
+
+	return TRUE;
+}
+
+/**
+ * e_vcard_attribute_get_value:
+ * @attr: an #EVCardAttribute
+ *
+ * Gets the value of a single-valued #EVCardAttribute, @attr.
+ *
+ * Return value: A newly allocated string representing the value.
+ **/
+char*
+e_vcard_attribute_get_value (EVCardAttribute *attr)
+{
+	GList *values;
+
+	g_return_val_if_fail (attr != NULL, NULL);
+
+	values = e_vcard_attribute_get_values (attr);
+
+	if (!e_vcard_attribute_is_single_valued (attr))
+		g_warning ("e_vcard_attribute_get_value called on multivalued attribute %s", attr->name);
+
+	return values ? g_strdup ((char*)values->data) : NULL;
+}
+
+/**
+ * e_vcard_attribute_get_value_decoded:
+ * @attr: an #EVCardAttribute
+ *
+ * Gets the value of a single-valued #EVCardAttribute, @attr, decoding
+ * it if necessary.
+ *
+ * Return value: A newly allocated #GString representing the value.
+ **/
+GString*
+e_vcard_attribute_get_value_decoded (EVCardAttribute *attr)
+{
+	GList *values;
+	GString *str = NULL;
+
+	g_return_val_if_fail (attr != NULL, NULL);
+
+	values = e_vcard_attribute_get_values_decoded (attr);
+
+	if (!e_vcard_attribute_is_single_valued (attr))
+		g_warning ("e_vcard_attribute_get_value_decoded called on multivalued attribute");
+
+	if (values)
+		str = values->data;
+
+	return str ? g_string_new_len (str->str, str->len) : NULL;
+}
+
+/**
+ * e_vcard_attribute_has_type:
+ * @attr: an #EVCardAttribute
+ * @typestr: a string representing the type
+ *
+ * Checks if @attr has an #EVCardAttributeParam of the specified type.
+ *
+ * Return value: %TRUE if such a parameter exists, %FALSE otherwise.
+ **/
+gboolean
+e_vcard_attribute_has_type (EVCardAttribute *attr, const char *typestr)
+{
+	GList *params;
+	GList *p;
+
+	g_return_val_if_fail (attr != NULL, FALSE);
+	g_return_val_if_fail (typestr != NULL, FALSE);
+
+	params = e_vcard_attribute_get_params (attr);
+
+	for (p = params; p; p = p->next) {
+		EVCardAttributeParam *param = p->data;
+
+		if (!g_ascii_strcasecmp (e_vcard_attribute_param_get_name (param), EVC_TYPE)) {
+			GList *values = e_vcard_attribute_param_get_values (param);
+			GList *v;
+
+			for (v = values; v; v = v->next) {
+				if (!g_ascii_strcasecmp ((char*)v->data, typestr))
+					return TRUE;
+			}
+		}
+	}
+
+	return FALSE;
+}
+
+/**
+ * e_vcard_attribute_get_params:
+ * @attr: an #EVCardAttribute
+ * 
+ * Gets the list of parameters from @attr. The list and its
+ * contents are owned by @attr, and must not be freed.
+ *
+ * Return value: A list of elements of type #EVCardAttributeParam.
+ **/
+GList*
+e_vcard_attribute_get_params (EVCardAttribute *attr)
+{
+	g_return_val_if_fail (attr != NULL, NULL);
+
+	return attr->params;
+}
+
+GList *
+e_vcard_attribute_get_param (EVCardAttribute *attr, const char *name)
+{
+	GList *params, *p;
+	
+	g_return_val_if_fail (attr != NULL, FALSE);
+	g_return_val_if_fail (name != NULL, FALSE);
+	
+	params = e_vcard_attribute_get_params (attr);
+
+	for (p = params; p; p = p->next) {
+		EVCardAttributeParam *param = p->data;
+		if (g_ascii_strcasecmp (e_vcard_attribute_param_get_name (param), name) == 0) {
+			return e_vcard_attribute_param_get_values (param);
+		}
+	}
+
+	return NULL;
+}
+
+/**
+ * e_vcard_attribute_param_get_name:
+ * @param: an #EVCardAttributeParam
+ *
+ * Gets the name of @param.
+ *
+ * Return value: The name of the parameter.
+ **/
+const char*
+e_vcard_attribute_param_get_name (EVCardAttributeParam *param)
+{
+	g_return_val_if_fail (param != NULL, NULL);
+
+	return param->name;
+}
+
+/**
+ * e_vcard_attribute_param_get_values:
+ * @param: an #EVCardAttributeParam
+ * 
+ * Gets the list of values from @param. The list and its
+ * contents are owned by @param, and must not be freed.
+ *
+ * Return value: A list of string elements representing the parameter's values.
+ **/
+GList*
+e_vcard_attribute_param_get_values (EVCardAttributeParam *param)
+{
+	g_return_val_if_fail (param != NULL, NULL);
+
+	return param->values;
+}
+
+
+
+/* encoding/decoding stuff ripped from camel-mime-utils.c */
+
+static char *_evc_base64_alphabet =
+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+static unsigned char _evc_base64_rank[256];
+
+static void
+_evc_base64_init(void)
+{
+	int i;
+
+	memset(_evc_base64_rank, 0xff, sizeof(_evc_base64_rank));
+	for (i=0;i<64;i++) {
+		_evc_base64_rank[(unsigned int)_evc_base64_alphabet[i]] = i;
+	}
+	_evc_base64_rank['='] = 0;
+}
+
+/* call this when finished encoding everything, to
+   flush off the last little bit */
+static size_t
+_evc_base64_encode_close(unsigned char *in, size_t inlen, gboolean break_lines, unsigned char *out, int *state, int *save)
+{
+	int c1, c2;
+	unsigned char *outptr = out;
+
+	if (inlen>0)
+		outptr += _evc_base64_encode_step(in, inlen, break_lines, outptr, state, save);
+
+	c1 = ((unsigned char *)save)[1];
+	c2 = ((unsigned char *)save)[2];
+	
+	d(printf("mode = %d\nc1 = %c\nc2 = %c\n",
+		 (int)((char *)save)[0],
+		 (int)((char *)save)[1],
+		 (int)((char *)save)[2]));
+
+	switch (((char *)save)[0]) {
+	case 2:
+		outptr[2] = _evc_base64_alphabet[ ( (c2 &0x0f) << 2 ) ];
+		g_assert(outptr[2] != 0);
+		goto skip;
+	case 1:
+		outptr[2] = '=';
+	skip:
+		outptr[0] = _evc_base64_alphabet[ c1 >> 2 ];
+		outptr[1] = _evc_base64_alphabet[ c2 >> 4 | ( (c1&0x3) << 4 )];
+		outptr[3] = '=';
+		outptr += 4;
+		break;
+	}
+	if (break_lines)
+		*outptr++ = '\n';
+
+	*save = 0;
+	*state = 0;
+
+	return outptr-out;
+}
+
+/*
+  performs an 'encode step', only encodes blocks of 3 characters to the
+  output at a time, saves left-over state in state and save (initialise to
+  0 on first invocation).
+*/
+static size_t
+_evc_base64_encode_step(unsigned char *in, size_t len, gboolean break_lines, unsigned char *out, int *state, int *save)
+{
+	register unsigned char *inptr, *outptr;
+
+	if (len<=0)
+		return 0;
+
+	inptr = in;
+	outptr = out;
+
+	d(printf("we have %d chars, and %d saved chars\n", len, ((char *)save)[0]));
+
+	if (len + ((char *)save)[0] > 2) {
+		unsigned char *inend = in+len-2;
+		register int c1, c2, c3;
+		register int already;
+
+		already = *state;
+
+		switch (((char *)save)[0]) {
+		case 1:	c1 = ((unsigned char *)save)[1]; goto skip1;
+		case 2:	c1 = ((unsigned char *)save)[1];
+			c2 = ((unsigned char *)save)[2]; goto skip2;
+		}
+		
+		/* yes, we jump into the loop, no i'm not going to change it, it's beautiful! */
+		while (inptr < inend) {
+			c1 = *inptr++;
+		skip1:
+			c2 = *inptr++;
+		skip2:
+			c3 = *inptr++;
+			*outptr++ = _evc_base64_alphabet[ c1 >> 2 ];
+			*outptr++ = _evc_base64_alphabet[ c2 >> 4 | ( (c1&0x3) << 4 ) ];
+			*outptr++ = _evc_base64_alphabet[ ( (c2 &0x0f) << 2 ) | (c3 >> 6) ];
+			*outptr++ = _evc_base64_alphabet[ c3 & 0x3f ];
+			/* this is a bit ugly ... */
+			if (break_lines && (++already)>=19) {
+				*outptr++='\n';
+				already = 0;
+			}
+		}
+
+		((char *)save)[0] = 0;
+		len = 2-(inptr-inend);
+		*state = already;
+	}
+
+	d(printf("state = %d, len = %d\n",
+		 (int)((char *)save)[0],
+		 len));
+
+	if (len>0) {
+		register char *saveout;
+
+		/* points to the slot for the next char to save */
+		saveout = & (((char *)save)[1]) + ((char *)save)[0];
+
+		/* len can only be 0 1 or 2 */
+		switch(len) {
+		case 2:	*saveout++ = *inptr++;
+		case 1:	*saveout++ = *inptr++;
+		}
+		((char *)save)[0]+=len;
+	}
+
+	d(printf("mode = %d\nc1 = %c\nc2 = %c\n",
+		 (int)((char *)save)[0],
+		 (int)((char *)save)[1],
+		 (int)((char *)save)[2]));
+
+	return outptr-out;
+}
+
+
+/**
+ * base64_decode_step: decode a chunk of base64 encoded data
+ * @in: input stream
+ * @len: max length of data to decode
+ * @out: output stream
+ * @state: holds the number of bits that are stored in @save
+ * @save: leftover bits that have not yet been decoded
+ *
+ * Decodes a chunk of base64 encoded data
+ **/
+static size_t
+_evc_base64_decode_step(unsigned char *in, size_t len, unsigned char *out, int *state, unsigned int *save)
+{
+	register unsigned char *inptr, *outptr;
+	unsigned char *inend, c;
+	register unsigned int v;
+	int i;
+
+	inend = in+len;
+	outptr = out;
+
+	/* convert 4 base64 bytes to 3 normal bytes */
+	v=*save;
+	i=*state;
+	inptr = in;
+	while (inptr<inend) {
+		c = _evc_base64_rank[*inptr++];
+		if (c != 0xff) {
+			v = (v<<6) | c;
+			i++;
+			if (i==4) {
+				*outptr++ = v>>16;
+				*outptr++ = v>>8;
+				*outptr++ = v;
+				i=0;
+			}
+		}
+	}
+
+	*save = v;
+	*state = i;
+
+	/* quick scan back for '=' on the end somewhere */
+	/* fortunately we can drop 1 output char for each trailing = (upto 2) */
+	i=2;
+	while (inptr>in && i) {
+		inptr--;
+		if (_evc_base64_rank[*inptr] != 0xff) {
+			if (*inptr == '=' && outptr>out)
+				outptr--;
+			i--;
+		}
+	}
+
+	/* if i!= 0 then there is a truncation error! */
+	return outptr-out;
+}
+
+static char *
+_evc_base64_encode_simple (const char *data, size_t len)
+{
+	unsigned char *out;
+	int state = 0, outlen;
+	int save = 0;
+
+	g_return_val_if_fail (data != NULL, NULL);
+
+	out = g_malloc (len * 4 / 3 + 5);
+	outlen = _evc_base64_encode_close ((unsigned char *)data, len, FALSE,
+				      out, &state, &save);
+	out[outlen] = 0;
+	return (char *)out;
+}
+
+static size_t
+_evc_base64_decode_simple (char *data, size_t len)
+{
+	int state = 0;
+	unsigned int save = 0;
+
+	g_return_val_if_fail (data != NULL, 0);
+
+	return _evc_base64_decode_step ((unsigned char *)data, len,
+					(unsigned char *)data, &state, &save);
+}
Index: addressbook/libebook-dbus/e-contact.h
===================================================================
--- addressbook/libebook-dbus/e-contact.h	(revision 409)
+++ addressbook/libebook-dbus/e-contact.h	(working copy)
@@ -1 +1,324 @@
-link ../libebook-orbit/./e-contact.h
\ No newline at end of file
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Authors:
+ *   Chris Toshok <toshok@ximian.com>
+ *
+ * Copyright (C) 2003 Ximian, Inc.
+ */
+
+#ifndef __E_CONTACT_H__
+#define __E_CONTACT_H__
+
+#include <time.h>
+#include <glib-object.h>
+#include <stdio.h>
+#include "e-vcard.h"
+
+G_BEGIN_DECLS
+
+#define E_TYPE_CONTACT            (e_contact_get_type ())
+#define E_CONTACT(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), E_TYPE_CONTACT, EContact))
+#define E_CONTACT_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), E_TYPE_CONTACT, EContactClass))
+#define E_IS_CONTACT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), E_TYPE_CONTACT))
+#define E_IS_CONTACT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), E_TYPE_CONTACT))
+#define E_CONTACT_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), E_TYPE_CONTACT, EContactClass))
+
+#define E_TYPE_CONTACT_DATE       (e_contact_date_get_type ())
+#define E_TYPE_CONTACT_NAME       (e_contact_name_get_type ())
+#define E_TYPE_CONTACT_PHOTO      (e_contact_photo_get_type ())
+#define E_TYPE_CONTACT_CERT       (e_contact_cert_get_type ())
+#define E_TYPE_CONTACT_ADDRESS    (e_contact_address_get_type ())
+
+typedef struct _EContact EContact;
+typedef struct _EContactClass EContactClass;
+typedef struct _EContactPrivate EContactPrivate;
+
+typedef enum {
+
+	E_CONTACT_UID = 1,     	 /* string field */
+	E_CONTACT_FILE_AS,     	 /* string field */
+	E_CONTACT_BOOK_URI,      /* string field */
+
+	/* Name fields */
+	E_CONTACT_FULL_NAME,   	 /* string field */
+	E_CONTACT_GIVEN_NAME,  	 /* synthetic string field */
+	E_CONTACT_FAMILY_NAME, 	 /* synthetic string field */
+	E_CONTACT_NICKNAME,    	 /* string field */
+
+	/* Email fields */
+	E_CONTACT_EMAIL_1,     	 /* synthetic string field */
+	E_CONTACT_EMAIL_2,     	 /* synthetic string field */
+	E_CONTACT_EMAIL_3,     	 /* synthetic string field */
+	E_CONTACT_EMAIL_4,       /* synthetic string field */
+
+	E_CONTACT_MAILER,        /* string field */
+
+	/* Address Labels */
+	E_CONTACT_ADDRESS_LABEL_HOME,  /* synthetic string field */
+	E_CONTACT_ADDRESS_LABEL_WORK,  /* synthetic string field */
+	E_CONTACT_ADDRESS_LABEL_OTHER, /* synthetic string field */
+
+	/* Phone fields */
+	E_CONTACT_PHONE_ASSISTANT,
+	E_CONTACT_PHONE_BUSINESS,
+	E_CONTACT_PHONE_BUSINESS_2,
+	E_CONTACT_PHONE_BUSINESS_FAX,
+	E_CONTACT_PHONE_CALLBACK,
+	E_CONTACT_PHONE_CAR,
+	E_CONTACT_PHONE_COMPANY,
+	E_CONTACT_PHONE_HOME,
+	E_CONTACT_PHONE_HOME_2,
+	E_CONTACT_PHONE_HOME_FAX,
+	E_CONTACT_PHONE_ISDN,
+	E_CONTACT_PHONE_MOBILE,
+	E_CONTACT_PHONE_OTHER,
+	E_CONTACT_PHONE_OTHER_FAX,
+	E_CONTACT_PHONE_PAGER,
+	E_CONTACT_PHONE_PRIMARY,
+	E_CONTACT_PHONE_RADIO,
+	E_CONTACT_PHONE_TELEX,
+	E_CONTACT_PHONE_TTYTDD,
+
+	/* Organizational fields */
+	E_CONTACT_ORG,        	 /* string field */
+	E_CONTACT_ORG_UNIT,   	 /* string field */
+	E_CONTACT_OFFICE,     	 /* string field */
+	E_CONTACT_TITLE,      	 /* string field */
+	E_CONTACT_ROLE,       	 /* string field */
+	E_CONTACT_MANAGER,    	 /* string field */
+	E_CONTACT_ASSISTANT,  	 /* string field */
+
+	/* Web fields */
+	E_CONTACT_HOMEPAGE_URL,  /* string field */
+	E_CONTACT_BLOG_URL,      /* string field */
+
+	/* Contact categories */
+	E_CONTACT_CATEGORIES,    /* string field */
+
+	/* Collaboration fields */
+	E_CONTACT_CALENDAR_URI,  /* string field */
+	E_CONTACT_FREEBUSY_URL,  /* string field */
+	E_CONTACT_ICS_CALENDAR,  /* string field */
+	E_CONTACT_VIDEO_URL,      /* string field */
+
+	/* misc fields */
+	E_CONTACT_SPOUSE,        /* string field */
+	E_CONTACT_NOTE,          /* string field */
+
+	E_CONTACT_IM_AIM_HOME_1,       /* Synthetic string field */
+	E_CONTACT_IM_AIM_HOME_2,       /* Synthetic string field */
+	E_CONTACT_IM_AIM_HOME_3,       /* Synthetic string field */
+	E_CONTACT_IM_AIM_WORK_1,       /* Synthetic string field */
+	E_CONTACT_IM_AIM_WORK_2,       /* Synthetic string field */
+	E_CONTACT_IM_AIM_WORK_3,       /* Synthetic string field */
+	E_CONTACT_IM_GROUPWISE_HOME_1, /* Synthetic string field */
+	E_CONTACT_IM_GROUPWISE_HOME_2, /* Synthetic string field */
+	E_CONTACT_IM_GROUPWISE_HOME_3, /* Synthetic string field */
+	E_CONTACT_IM_GROUPWISE_WORK_1, /* Synthetic string field */
+	E_CONTACT_IM_GROUPWISE_WORK_2, /* Synthetic string field */
+	E_CONTACT_IM_GROUPWISE_WORK_3, /* Synthetic string field */
+	E_CONTACT_IM_JABBER_HOME_1,    /* Synthetic string field */
+	E_CONTACT_IM_JABBER_HOME_2,    /* Synthetic string field */
+	E_CONTACT_IM_JABBER_HOME_3,    /* Synthetic string field */
+	E_CONTACT_IM_JABBER_WORK_1,    /* Synthetic string field */
+	E_CONTACT_IM_JABBER_WORK_2,    /* Synthetic string field */
+	E_CONTACT_IM_JABBER_WORK_3,    /* Synthetic string field */
+	E_CONTACT_IM_YAHOO_HOME_1,     /* Synthetic string field */
+	E_CONTACT_IM_YAHOO_HOME_2,     /* Synthetic string field */
+	E_CONTACT_IM_YAHOO_HOME_3,     /* Synthetic string field */
+	E_CONTACT_IM_YAHOO_WORK_1,     /* Synthetic string field */
+	E_CONTACT_IM_YAHOO_WORK_2,     /* Synthetic string field */
+	E_CONTACT_IM_YAHOO_WORK_3,     /* Synthetic string field */
+	E_CONTACT_IM_MSN_HOME_1,       /* Synthetic string field */
+	E_CONTACT_IM_MSN_HOME_2,       /* Synthetic string field */
+	E_CONTACT_IM_MSN_HOME_3,       /* Synthetic string field */
+	E_CONTACT_IM_MSN_WORK_1,       /* Synthetic string field */
+	E_CONTACT_IM_MSN_WORK_2,       /* Synthetic string field */
+	E_CONTACT_IM_MSN_WORK_3,       /* Synthetic string field */
+	E_CONTACT_IM_ICQ_HOME_1,       /* Synthetic string field */
+	E_CONTACT_IM_ICQ_HOME_2,       /* Synthetic string field */
+	E_CONTACT_IM_ICQ_HOME_3,       /* Synthetic string field */
+	E_CONTACT_IM_ICQ_WORK_1,       /* Synthetic string field */
+	E_CONTACT_IM_ICQ_WORK_2,       /* Synthetic string field */
+	E_CONTACT_IM_ICQ_WORK_3,       /* Synthetic string field */
+
+	/* Convenience field for getting a name from the contact.
+	   Returns the first one of [File-As, Full Name, Org, Email1]
+	   to be set */
+	E_CONTACT_REV,     /* string field to hold  time of last update to this vcard*/
+	E_CONTACT_NAME_OR_ORG,
+
+	/* Address fields */
+	E_CONTACT_ADDRESS,       /* Multi-valued structured (EContactAddress) */
+	E_CONTACT_ADDRESS_HOME,  /* synthetic structured field (EContactAddress) */
+	E_CONTACT_ADDRESS_WORK,  /* synthetic structured field (EContactAddress) */
+	E_CONTACT_ADDRESS_OTHER, /* synthetic structured field (EContactAddress) */
+
+	E_CONTACT_CATEGORY_LIST, /* multi-valued */
+
+	/* Photo/Logo */
+	E_CONTACT_PHOTO,       	 /* structured field (EContactPhoto) */
+	E_CONTACT_LOGO,       	 /* structured field (EContactPhoto) */
+
+	E_CONTACT_NAME,        	 /* structured field (EContactName) */
+	E_CONTACT_EMAIL,       	 /* Multi-valued */
+
+	/* Instant Messaging fields */
+	E_CONTACT_IM_AIM,     	 /* Multi-valued */
+	E_CONTACT_IM_GROUPWISE,  /* Multi-valued */
+	E_CONTACT_IM_JABBER,  	 /* Multi-valued */
+	E_CONTACT_IM_YAHOO,   	 /* Multi-valued */
+	E_CONTACT_IM_MSN,     	 /* Multi-valued */
+	E_CONTACT_IM_ICQ,     	 /* Multi-valued */
+       
+	E_CONTACT_WANTS_HTML,    /* boolean field */
+
+	/* fields used for describing contact lists.  a contact list
+	   is just a contact with _IS_LIST set to true.  the members
+	   are listed in the _EMAIL field. */
+	E_CONTACT_IS_LIST,             /* boolean field */
+	E_CONTACT_LIST_SHOW_ADDRESSES, /* boolean field */
+
+
+	E_CONTACT_BIRTH_DATE,    /* structured field (EContactDate) */
+	E_CONTACT_ANNIVERSARY,   /* structured field (EContactDate) */
+
+	/* Security Fields */
+	E_CONTACT_X509_CERT,     /* structured field (EContactCert) */
+
+	E_CONTACT_OSSO_CONTACT_STATE, /* list of strings */
+
+	E_CONTACT_FIELD_LAST,
+	E_CONTACT_FIELD_FIRST        = E_CONTACT_UID,
+
+	/* useful constants */
+	E_CONTACT_LAST_SIMPLE_STRING = E_CONTACT_NAME_OR_ORG,
+	E_CONTACT_FIRST_PHONE_ID     = E_CONTACT_PHONE_ASSISTANT,
+	E_CONTACT_LAST_PHONE_ID      = E_CONTACT_PHONE_TTYTDD,
+	E_CONTACT_FIRST_EMAIL_ID     = E_CONTACT_EMAIL_1,
+	E_CONTACT_LAST_EMAIL_ID      = E_CONTACT_EMAIL_4,
+	E_CONTACT_FIRST_ADDRESS_ID   = E_CONTACT_ADDRESS_HOME,
+	E_CONTACT_LAST_ADDRESS_ID    = E_CONTACT_ADDRESS_OTHER,
+	E_CONTACT_FIRST_LABEL_ID     = E_CONTACT_ADDRESS_LABEL_HOME,
+	E_CONTACT_LAST_LABEL_ID      = E_CONTACT_ADDRESS_LABEL_OTHER
+
+} EContactField;
+
+typedef struct {
+	char *family;
+	char *given;
+	char *additional;
+ 	char *prefixes;
+	char *suffixes;
+} EContactName;
+
+typedef enum {
+	E_CONTACT_PHOTO_TYPE_INLINED,
+	E_CONTACT_PHOTO_TYPE_URI
+} EContactPhotoType;
+
+typedef struct {
+	EContactPhotoType type;
+	union {
+		struct {
+			char *mime_type;
+			int length;
+			guchar *data;
+		} inlined;
+		char *uri;
+	} data;
+} EContactPhoto;
+
+typedef struct {
+	char *address_format; /* the two letter country code that
+				 determines the format/meaning of the
+				 following fields */
+	char *po;
+	char *ext;
+	char *street;
+	char *locality;
+	char *region;
+	char *code;
+	char *country;
+} EContactAddress;
+
+typedef struct {
+	int year;
+	int month;
+	int day;
+} EContactDate;
+
+typedef struct {
+	int length;
+	char *data;
+} EContactCert;
+
+struct _EContact {
+	EVCard parent;
+	/*< private >*/
+	EContactPrivate *priv;
+};
+
+struct _EContactClass {
+	EVCardClass parent_class;
+
+	/* Padding for future expansion */
+	void (*_ebook_reserved0) (void);
+	void (*_ebook_reserved1) (void);
+	void (*_ebook_reserved2) (void);
+	void (*_ebook_reserved3) (void);
+	void (*_ebook_reserved4) (void);
+};
+
+GType                   e_contact_get_type (void);
+
+EContact*               e_contact_new              (void);
+EContact*               e_contact_new_from_vcard   (const char *vcard);
+
+EContact*               e_contact_duplicate        (EContact *contact);
+
+gpointer                e_contact_get              (EContact *contact, EContactField field_id);
+gconstpointer		e_contact_get_const        (EContact *contact, EContactField field_id);
+void                    e_contact_set              (EContact *contact, EContactField field_id, gpointer value);
+
+/* the following two calls return and take a GList of
+   EVCardAttribute*'s. */
+GList*                  e_contact_get_attributes   (EContact *contact, EContactField field_id);
+void                    e_contact_set_attributes   (EContact *contact, EContactField field_id, GList *attributes);
+
+/* misc functions for structured values */
+GType                   e_contact_date_get_type    (void);
+EContactDate           *e_contact_date_new         (void);
+EContactDate           *e_contact_date_from_string (const char *str);
+char                   *e_contact_date_to_string   (EContactDate *dt);
+gboolean                e_contact_date_equal       (EContactDate *dt1,
+						    EContactDate *dt2);
+void                    e_contact_date_free        (EContactDate *date);
+
+GType                   e_contact_name_get_type    (void);
+EContactName           *e_contact_name_new         (void);
+char                   *e_contact_name_to_string   (const EContactName *name);
+EContactName           *e_contact_name_from_string (const char *name_str);
+EContactName           *e_contact_name_copy        (EContactName *name);
+void                    e_contact_name_free        (EContactName *name);
+
+
+GType                   e_contact_photo_get_type   (void);
+void                    e_contact_photo_free       (EContactPhoto *photo);
+
+GType                   e_contact_cert_get_type    (void);
+void                    e_contact_cert_free        (EContactCert *cert);
+
+GType                   e_contact_address_get_type (void);
+void                    e_contact_address_free     (EContactAddress *address);
+
+
+const char*             e_contact_field_name       (EContactField field_id);
+const char*             e_contact_pretty_name      (EContactField field_id);
+const char*             e_contact_vcard_attribute  (EContactField field_id);
+EContactField           e_contact_field_id         (const char *field_name);
+EContactField           e_contact_field_id_from_vcard (const char *vcard_field);
+
+G_END_DECLS
+
+#endif /* __E_CONTACT_H__ */
Index: addressbook/libebook-dbus/e-book-types.h
===================================================================
--- addressbook/libebook-dbus/e-book-types.h	(revision 409)
+++ addressbook/libebook-dbus/e-book-types.h	(working copy)
@@ -1 +1,76 @@
-link ../libebook-orbit/e-book-types.h
\ No newline at end of file
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * A client-side GObject which exposes the
+ * Evolution:BookListener interface.
+ *
+ * Author:
+ *   Nat Friedman (nat@ximian.com)
+ *
+ * Copyright 2000, Ximian, Inc.
+ */
+
+#ifndef __E_BOOK_TYPES_H__
+#define __E_BOOK_TYPES_H__
+
+#include <glib.h>
+#include <libebook/e-contact.h>
+
+G_BEGIN_DECLS
+
+#define E_BOOK_ERROR e_book_error_quark()
+
+GQuark e_book_error_quark (void) G_GNUC_CONST;
+
+typedef enum {
+	E_BOOK_ERROR_OK,
+	E_BOOK_ERROR_INVALID_ARG,
+	E_BOOK_ERROR_BUSY,
+	E_BOOK_ERROR_REPOSITORY_OFFLINE,
+	E_BOOK_ERROR_NO_SUCH_BOOK,
+	E_BOOK_ERROR_NO_SELF_CONTACT,
+	E_BOOK_ERROR_SOURCE_NOT_LOADED,
+	E_BOOK_ERROR_SOURCE_ALREADY_LOADED,
+	E_BOOK_ERROR_PERMISSION_DENIED,
+	E_BOOK_ERROR_CONTACT_NOT_FOUND,
+	E_BOOK_ERROR_CONTACT_ID_ALREADY_EXISTS,
+	E_BOOK_ERROR_PROTOCOL_NOT_SUPPORTED,
+	E_BOOK_ERROR_CANCELLED,
+	E_BOOK_ERROR_COULD_NOT_CANCEL,
+	E_BOOK_ERROR_AUTHENTICATION_FAILED,
+	E_BOOK_ERROR_AUTHENTICATION_REQUIRED,
+	E_BOOK_ERROR_TLS_NOT_AVAILABLE,
+	E_BOOK_ERROR_CORBA_EXCEPTION,
+	E_BOOK_ERROR_NO_SUCH_SOURCE,
+	E_BOOK_ERROR_OFFLINE_UNAVAILABLE,
+	E_BOOK_ERROR_OTHER_ERROR,
+	E_BOOK_ERROR_INVALID_SERVER_VERSION
+} EBookStatus;
+
+
+typedef enum {
+	E_BOOK_VIEW_STATUS_OK,
+	E_BOOK_VIEW_STATUS_TIME_LIMIT_EXCEEDED,
+	E_BOOK_VIEW_STATUS_SIZE_LIMIT_EXCEEDED,
+	E_BOOK_VIEW_ERROR_INVALID_QUERY,
+	E_BOOK_VIEW_ERROR_QUERY_REFUSED,
+	E_BOOK_VIEW_ERROR_OTHER_ERROR
+} EBookViewStatus;
+
+typedef enum {
+	E_BOOK_CHANGE_CARD_ADDED,
+	E_BOOK_CHANGE_CARD_DELETED,
+	E_BOOK_CHANGE_CARD_MODIFIED
+} EBookChangeType;
+
+typedef struct {
+	EBookChangeType  change_type;
+	EContact        *contact;
+} EBookChange;
+
+/* Convenience defines to save typing */
+#define E_PARAM_READABLE G_PARAM_READABLE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB
+#define E_PARAM_READWRITE G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB
+
+G_END_DECLS
+
+#endif /* ! __E_BOOK_TYPES_H__ */
Index: addressbook/libebook-dbus/e-book-query.h
===================================================================
--- addressbook/libebook-dbus/e-book-query.h	(revision 409)
+++ addressbook/libebook-dbus/e-book-query.h	(working copy)
@@ -1 +1,57 @@
-link ../libebook-orbit/./e-book-query.h
\ No newline at end of file
+
+#ifndef __E_BOOK_QUERY_H__
+#define __E_BOOK_QUERY_H__
+
+#include "e-contact.h"
+
+G_BEGIN_DECLS
+
+#define E_TYPE_BOOK_QUERY (e_book_query_get_type ())
+
+typedef struct EBookQuery EBookQuery;
+
+typedef enum {
+  E_BOOK_QUERY_IS,
+  E_BOOK_QUERY_CONTAINS,
+  E_BOOK_QUERY_BEGINS_WITH,
+  E_BOOK_QUERY_ENDS_WITH,
+
+  /*
+    Consider these "coming soon". 
+
+    E_BOOK_QUERY_LT,
+    E_BOOK_QUERY_LE,
+    E_BOOK_QUERY_GT,
+    E_BOOK_QUERY_GE,
+    E_BOOK_QUERY_EQ,
+  */
+} EBookQueryTest;
+
+EBookQuery* e_book_query_from_string  (const char *query_string);
+char*       e_book_query_to_string    (EBookQuery *q);
+
+EBookQuery* e_book_query_ref          (EBookQuery *q);
+void        e_book_query_unref        (EBookQuery *q);
+
+EBookQuery* e_book_query_and          (int nqs, EBookQuery **qs, gboolean unref);
+EBookQuery* e_book_query_andv         (EBookQuery *q, ...);
+EBookQuery* e_book_query_or           (int nqs, EBookQuery **qs, gboolean unref);
+EBookQuery* e_book_query_orv          (EBookQuery *q, ...);
+
+EBookQuery* e_book_query_not          (EBookQuery *q, gboolean unref);
+
+EBookQuery* e_book_query_field_exists (EContactField   field);
+EBookQuery *e_book_query_vcard_field_exists (const char *field);
+EBookQuery* e_book_query_field_test   (EContactField   field,
+				       EBookQueryTest     test,
+				       const char        *value);
+
+/* a special any field contains query */
+EBookQuery* e_book_query_any_field_contains (const char  *value);
+
+GType       e_book_query_get_type (void);
+EBookQuery* e_book_query_copy     (EBookQuery *q);
+
+G_END_DECLS
+
+#endif /* __E_BOOK_QUERY_H__ */
Index: addressbook/libebook-dbus/e-vcard.h
===================================================================
--- addressbook/libebook-dbus/e-vcard.h	(revision 409)
+++ addressbook/libebook-dbus/e-vcard.h	(working copy)
@@ -1 +1,204 @@
-link ../libebook-orbit/./e-vcard.h
\ No newline at end of file
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* e-vcard.h
+ *
+ * Copyright (C) 2003 Ximian, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Chris Toshok (toshok@ximian.com)
+ */
+
+#ifndef _EVCARD_H
+#define _EVCARD_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define EVC_ADR             "ADR"
+#define EVC_BDAY            "BDAY"
+#define EVC_CALURI          "CALURI"
+#define EVC_CATEGORIES      "CATEGORIES"
+#define EVC_EMAIL           "EMAIL"
+#define EVC_ENCODING        "ENCODING"
+#define EVC_FBURL           "FBURL"
+#define EVC_FN              "FN"
+#define EVC_ICSCALENDAR     "ICSCALENDAR" /* XXX should this be X-EVOLUTION-ICSCALENDAR? */
+#define EVC_KEY             "KEY"
+#define EVC_LABEL           "LABEL"
+#define EVC_LOGO            "LOGO"
+#define EVC_MAILER          "MAILER"
+#define EVC_NICKNAME        "NICKNAME"
+#define EVC_N               "N"
+#define EVC_NOTE            "NOTE"
+#define EVC_ORG             "ORG"
+#define EVC_PHOTO           "PHOTO"
+#define EVC_PRODID          "PRODID"
+#define EVC_QUOTEDPRINTABLE "QUOTED-PRINTABLE"
+#define EVC_REV             "REV"
+#define EVC_ROLE            "ROLE"
+#define EVC_TEL             "TEL"
+#define EVC_TITLE           "TITLE"
+#define EVC_TYPE            "TYPE"
+#define EVC_UID             "UID"
+#define EVC_URL             "URL"
+#define EVC_VALUE           "VALUE"
+#define EVC_VERSION         "VERSION"
+
+#define EVC_X_AIM              "X-AIM"
+#define EVC_X_ANNIVERSARY      "X-EVOLUTION-ANNIVERSARY"
+#define EVC_X_ASSISTANT        "X-EVOLUTION-ASSISTANT"
+#define EVC_X_BIRTHDAY         "X-EVOLUTION-BIRTHDAY"
+#define EVC_X_BLOG_URL         "X-EVOLUTION-BLOG-URL"
+#define EVC_X_CALLBACK         "X-EVOLUTION-CALLBACK"
+#define EVC_X_COMPANY          "X-EVOLUTION-COMPANY"
+#define EVC_X_DEST_CONTACT_UID "X-EVOLUTION-DEST-CONTACT-UID"
+#define EVC_X_DEST_EMAIL       "X-EVOLUTION-DEST-EMAIL"
+#define EVC_X_DEST_EMAIL_NUM   "X-EVOLUTION-DEST-EMAIL-NUM"
+#define EVC_X_DEST_HTML_MAIL   "X-EVOLUTION-DEST-HTML-MAIL"
+#define EVC_X_DEST_NAME        "X-EVOLUTION-DEST-NAME"
+#define EVC_X_DEST_SOURCE_UID  "X-EVOLUTION-DEST-SOURCE-UID"
+#define EVC_X_FILE_AS          "X-EVOLUTION-FILE-AS"
+#define EVC_X_ICQ              "X-ICQ"
+#define EVC_X_JABBER           "X-JABBER"
+#define EVC_X_LIST_SHOW_ADDRESSES "X-EVOLUTION-LIST-SHOW_ADDRESSES"
+#define EVC_X_LIST          	"X-EVOLUTION-LIST"
+#define EVC_X_MANAGER       	"X-EVOLUTION-MANAGER"
+#define EVC_X_MSN           	"X-MSN"
+#define EVC_X_RADIO         	"X-EVOLUTION-RADIO"
+#define EVC_X_SPOUSE        	"X-EVOLUTION-SPOUSE"
+#define EVC_X_TELEX         	"X-EVOLUTION-TELEX"
+#define EVC_X_TTYTDD        	"X-EVOLUTION-TTYTDD"
+#define EVC_X_VIDEO_URL     	"X-EVOLUTION-VIDEO-URL"
+#define EVC_X_WANTS_HTML    	"X-MOZILLA-HTML"
+#define EVC_X_YAHOO         	"X-YAHOO"
+#define EVC_X_GROUPWISE     	"X-GROUPWISE"
+#define EVC_X_BOOK_URI     	"X-EVOLUTION-BOOK-URI"
+#define EVC_X_OSSO_CONTACT_STATE "X-OSSO-CONTACT-STATE"
+
+/* Parameter names */
+#define EVC_X_OSSO_BOUND "X-OSSO-BOUND"
+#define EVC_X_OSSO_FIELD_STATE "X-OSSO-FIELD-STATE"
+
+typedef enum {
+	EVC_FORMAT_VCARD_21,
+	EVC_FORMAT_VCARD_30
+} EVCardFormat;
+
+#define E_TYPE_VCARD            (e_vcard_get_type ())
+#define E_VCARD(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), E_TYPE_VCARD, EVCard))
+#define E_VCARD_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), E_TYPE_VCARD, EVCardClass))
+#define E_IS_VCARD(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), E_TYPE_VCARD))
+#define E_IS_VCARD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), E_TYPE_VCARD))
+#define E_VCARD_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), E_TYPE_VCARD, EVCardClass))
+
+typedef struct _EVCard EVCard;
+typedef struct _EVCardClass EVCardClass;
+typedef struct _EVCardPrivate EVCardPrivate;
+typedef struct _EVCardAttribute EVCardAttribute;
+typedef struct _EVCardAttributeParam EVCardAttributeParam;
+
+struct _EVCard {
+	GObject parent;
+	/*< private >*/
+	EVCardPrivate *priv;
+};
+
+struct _EVCardClass {
+	GObjectClass parent_class;
+
+	/* Padding for future expansion */
+	void (*_ebook_reserved0) (void);
+	void (*_ebook_reserved1) (void);
+	void (*_ebook_reserved2) (void);
+	void (*_ebook_reserved3) (void);
+	void (*_ebook_reserved4) (void);
+};
+
+GType   e_vcard_get_type                     (void);
+
+void    e_vcard_construct                    (EVCard *evc, const char *str);
+EVCard* e_vcard_new                          (void);
+EVCard* e_vcard_new_from_string              (const char *str);
+
+char*   e_vcard_to_string                    (EVCard *evc, EVCardFormat format);
+
+/* mostly for debugging */
+void    e_vcard_dump_structure               (EVCard *evc);
+
+
+/* attributes */
+EVCardAttribute *e_vcard_attribute_new               (const char *attr_group, const char *attr_name);
+void             e_vcard_attribute_free              (EVCardAttribute *attr);
+EVCardAttribute *e_vcard_attribute_copy              (EVCardAttribute *attr);
+void             e_vcard_remove_attributes           (EVCard *evcard, const char *attr_group, const char *attr_name);
+void             e_vcard_remove_attribute            (EVCard *evcard, EVCardAttribute *attr);
+void             e_vcard_add_attribute               (EVCard *evcard, EVCardAttribute *attr);
+void             e_vcard_add_attribute_with_value    (EVCard *evcard, EVCardAttribute *attr, const char *value);
+void             e_vcard_add_attribute_with_values   (EVCard *evcard, EVCardAttribute *attr, ...);
+void             e_vcard_attribute_add_value         (EVCardAttribute *attr, const char *value);
+void             e_vcard_attribute_add_value_decoded (EVCardAttribute *attr, const char *value, int len);
+void             e_vcard_attribute_add_values        (EVCardAttribute *attr, ...);
+void             e_vcard_attribute_remove_value      (EVCardAttribute *attr, const char *s);
+void             e_vcard_attribute_remove_values     (EVCardAttribute *attr);
+void             e_vcard_attribute_remove_params     (EVCardAttribute *attr);
+void             e_vcard_attribute_remove_param_value (EVCardAttribute *attr, const char *param_name, const char *s);
+
+/* attribute parameters */
+EVCardAttributeParam* e_vcard_attribute_param_new             (const char *param_name);
+void                  e_vcard_attribute_param_free            (EVCardAttributeParam *param);
+EVCardAttributeParam* e_vcard_attribute_param_copy            (EVCardAttributeParam *param);
+void                  e_vcard_attribute_add_param             (EVCardAttribute *attr, EVCardAttributeParam *param);
+void                  e_vcard_attribute_add_param_with_value  (EVCardAttribute *attr,
+							       EVCardAttributeParam *param, const char *value);
+void                  e_vcard_attribute_add_param_with_values (EVCardAttribute *attr,
+							       EVCardAttributeParam *param, ...);
+
+void                  e_vcard_attribute_param_add_value       (EVCardAttributeParam *param,
+							       const char *value);
+void                  e_vcard_attribute_param_add_values      (EVCardAttributeParam *param,
+							       ...);
+void                  e_vcard_attribute_param_remove_values   (EVCardAttributeParam *param);
+
+/* EVCard* accessors.  nothing returned from these functions should be
+   freed by the caller. */
+EVCardAttribute *e_vcard_get_attribute (EVCard *vcard, const char *name);
+GList*           e_vcard_get_attributes       (EVCard *evcard);
+const char*      e_vcard_attribute_get_group  (EVCardAttribute *attr);
+const char*      e_vcard_attribute_get_name   (EVCardAttribute *attr);
+GList*           e_vcard_attribute_get_values (EVCardAttribute *attr);  /* GList elements are of type char* */
+GList*           e_vcard_attribute_get_values_decoded (EVCardAttribute *attr); /* GList elements are of type GString* */
+
+/* special accessors for single valued attributes */
+gboolean              e_vcard_attribute_is_single_valued      (EVCardAttribute *attr);
+char*                 e_vcard_attribute_get_value             (EVCardAttribute *attr);
+GString*              e_vcard_attribute_get_value_decoded     (EVCardAttribute *attr);
+
+GList*           e_vcard_attribute_get_params       (EVCardAttribute *attr);
+GList*           e_vcard_attribute_get_param        (EVCardAttribute *attr, const char *name);
+const char*      e_vcard_attribute_param_get_name   (EVCardAttributeParam *param);
+GList*           e_vcard_attribute_param_get_values (EVCardAttributeParam *param);
+
+/* special TYPE= parameter predicate (checks for TYPE=@typestr */
+gboolean         e_vcard_attribute_has_type         (EVCardAttribute *attr, const char *typestr);
+
+/* Utility functions. */
+char*            e_vcard_escape_string (const char *str);
+char*            e_vcard_unescape_string (const char *str);
+
+G_END_DECLS
+
+#endif /* _EVCARD_H */
Index: addressbook/libebook-dbus/e-name-western.c
===================================================================
--- addressbook/libebook-dbus/e-name-western.c	(revision 409)
+++ addressbook/libebook-dbus/e-name-western.c	(working copy)
@@ -1 +1,990 @@
-link ../libebook-orbit/./e-name-western.c
\ No newline at end of file
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * A simple Western name parser.
+ *
+ * <Nat> Jamie, do you know anything about name parsing?
+ * <jwz> Are you going down that rat hole?  Bring a flashlight.
+ *
+ * Authors:
+ *   Nat Friedman <nat@ximian.com>
+ *
+ * Copyright 1999 - 2001, Ximian, Inc.
+ */
+
+#include <ctype.h>
+#include <string.h>
+#include <glib.h>
+ 
+#include "e-name-western.h"
+#include "e-name-western-tables.h"
+
+typedef struct {
+	int prefix_idx;
+	int first_idx;
+	int middle_idx;
+	int nick_idx;
+	int last_idx;
+	int suffix_idx;
+} ENameWesternIdxs;
+
+static int
+e_name_western_str_count_words (const char *str)
+{
+	int word_count;
+	const char *p;
+
+	word_count = 0;
+
+	for (p = str; p != NULL; p = g_utf8_strchr (p, -1, ' ')) {
+		word_count ++;
+		p = g_utf8_next_char (p);
+	}
+
+	return word_count;
+}
+
+static void
+e_name_western_cleanup_string (char **str)
+{
+	char *newstr;
+	char *p;
+
+	if (*str == NULL)
+		return;
+
+	/* skip any spaces and commas at the start of the string */
+	p = *str;
+	while (g_unichar_isspace (g_utf8_get_char(p)) || *p == ',')
+		p = g_utf8_next_char (p);
+
+	/* make the copy we're going to return */
+	newstr = g_strdup (p);
+
+	if ( strlen(newstr) > 0) {
+		/* now search from the back, skipping over any spaces and commas */
+		p = newstr + strlen (newstr);
+		p = g_utf8_prev_char (p);
+		while (g_unichar_isspace (g_utf8_get_char(p)) || *p == ',')
+			p = g_utf8_prev_char (p);
+		/* advance p to after the character that caused us to exit the
+		   previous loop, and end the string. */
+		if ((! g_unichar_isspace (g_utf8_get_char (p))) && *p != ',')
+			p = g_utf8_next_char (p);
+		*p = '\0';
+	}
+
+	g_free (*str);
+	*str = newstr;
+}
+
+static char *
+e_name_western_get_words_at_idx (char *str, int idx, int num_words)
+{
+	GString *words;
+	char *p;
+	int   word_count;
+
+	/*
+	 * Walk to the end of the words.
+	 */
+	words = g_string_new ("");
+	word_count = 0;
+	p = str + idx;
+	while (word_count < num_words && *p != '\0') {
+		while (! g_unichar_isspace (g_utf8_get_char (p)) && *p != '\0') {
+			words = g_string_append_unichar (words, g_utf8_get_char (p));
+			p = g_utf8_next_char (p);
+		}
+
+		while (g_unichar_isspace (g_utf8_get_char (p)) && *p != '\0')
+			p = g_utf8_next_char (p);
+
+		word_count ++;
+	}
+
+	return g_string_free (words, FALSE);
+}
+
+/*
+ * What the fuck is wrong with glib's MAX macro.
+ */ 
+static int
+e_name_western_max (const int a, const int b)
+{
+	if (a > b)
+		return a;
+
+	return b;
+}
+
+static gboolean
+e_name_western_word_is_suffix (char *word)
+{
+	int i;
+
+	for (i = 0; i < G_N_ELEMENTS (western_sfx_index); i++) {
+		const char *suffix;
+		int length;
+
+		suffix = western_sfx_table + western_sfx_index[i];
+		length = strlen (suffix);
+
+		if (!g_strcasecmp (word, suffix) || 
+		    ( !g_strncasecmp (word, suffix, length) &&
+		      strlen(word) == length + 1 &&
+		      word[length] == '.' ))
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
+static char *
+e_name_western_get_one_prefix_at_str (char *str)
+{
+	char *word;
+	int   i;
+
+	/*
+	 * Check for prefixes from our table.
+	 */
+	for (i = 0; i < G_N_ELEMENTS (western_pfx_index); i++) {
+		int pfx_words;
+		const char *prefix;
+		char *words;
+
+		prefix = western_pfx_table + western_pfx_index[i];
+		pfx_words = e_name_western_str_count_words (prefix);
+		words = e_name_western_get_words_at_idx (str, 0, pfx_words);
+
+		if (! g_strcasecmp (words, prefix))
+			return words;
+
+		g_free (words);
+	}
+
+	/*
+	 * Check for prefixes we don't know about.  These are always a
+	 * sequence of more than one letters followed by a period.
+	 */
+	word = e_name_western_get_words_at_idx (str, 0, 1);
+
+	if (g_utf8_strlen (word, -1) > 2 && 
+	    g_unichar_isalpha (g_utf8_get_char (word)) &&
+	    g_unichar_isalpha (g_utf8_get_char (g_utf8_next_char (word))) &&
+	    word [strlen (word) - 1] == '.')
+		return word;
+
+	g_free (word);
+
+	return NULL;
+}
+
+static char *
+e_name_western_get_prefix_at_str (char *str)
+{
+	char *pfx;
+	char *pfx1;
+	char *pfx2;
+	char *p;
+
+	/* Get the first prefix. */
+	pfx1 = e_name_western_get_one_prefix_at_str (str);
+
+	if (pfx1 == NULL)
+		return NULL;
+
+	/* Check for a second prefix. */
+	p = str + strlen (pfx1);
+	while (g_unichar_isspace (g_utf8_get_char (p)) && *p != '\0')
+		p = g_utf8_next_char (p);
+
+	pfx2 = e_name_western_get_one_prefix_at_str (p);
+
+	if (pfx2 != NULL) {
+		int pfx_len;
+
+		pfx_len = (p + strlen (pfx2)) - str;
+		pfx = g_malloc0 (pfx_len + 1);
+		strncpy (pfx, str, pfx_len);
+	} else {
+		pfx = g_strdup (pfx1);
+	}
+
+	g_free (pfx1);
+	g_free (pfx2);
+
+	return pfx;
+}
+
+static void
+e_name_western_extract_prefix (ENameWestern *name, ENameWesternIdxs *idxs)
+{
+	char *pfx;
+
+	pfx = e_name_western_get_prefix_at_str (name->full);
+
+	if (pfx == NULL)
+		return;
+
+	idxs->prefix_idx = 0;
+	name->prefix     = pfx;
+}
+
+static gboolean
+e_name_western_is_complex_last_beginning (char *word)
+{
+	int i;
+
+	for (i = 0; i < G_N_ELEMENTS (western_complex_last_index); i++) {
+		const char *last = western_complex_last_table + western_complex_last_index[i];
+		if (! g_strcasecmp (word, last))
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
+static void
+e_name_western_extract_first (ENameWestern *name, ENameWesternIdxs *idxs)
+{
+	/*
+	 * If there's a prefix, then the first name is right after it.
+	 */
+	if (idxs->prefix_idx != -1) {
+		int   first_idx;
+		char *p;
+
+		first_idx = idxs->prefix_idx + strlen (name->prefix);
+
+		/* Skip past white space. */
+		p = name->full + first_idx;
+		while (g_unichar_isspace (g_utf8_get_char (p)) && *p != '\0')
+			p = g_utf8_next_char (p);
+
+		if (*p == '\0')
+			return;
+
+		idxs->first_idx = p - name->full;
+		name->first = e_name_western_get_words_at_idx (
+			name->full, idxs->first_idx, 1);
+
+	} else {
+
+		/*
+		 * Otherwise, the first name is probably the first string.
+		 */
+		idxs->first_idx = 0;
+		name->first = e_name_western_get_words_at_idx (
+			name->full, idxs->first_idx, 1);
+	}
+
+	/*
+	 * Check that we didn't just assign the beginning of a
+	 * compound last name to the first name.
+	 */
+	if (name->first != NULL) {
+		if (e_name_western_is_complex_last_beginning (name->first)) {
+			g_free (name->first);
+			name->first = NULL;
+			idxs->first_idx = -1;
+		}
+	}
+}
+
+static void
+e_name_western_extract_middle (ENameWestern *name, ENameWesternIdxs *idxs)
+{
+	char *word;
+	char *middle;
+
+	/*
+	 * Middle names can only exist if you have a first name.
+	 */
+	if (idxs->first_idx == -1)
+		return;
+
+	middle = name->full + idxs->first_idx + strlen (name->first);
+	if (*middle == '\0')
+		return;
+
+	middle = g_utf8_next_char (middle);
+	if (*middle == '\0')
+		return;
+	
+	/*
+	 * Search for the first space (or the terminating \0)
+	 */
+	while (g_unichar_isspace (g_utf8_get_char (middle)) &&
+	       *middle != '\0')
+		middle = g_utf8_next_char (middle);
+		
+	if (*middle == '\0')
+		return;
+
+	/*
+	 * Skip past the nickname, if it's there.
+	 */
+	if (*middle == '\"') {
+		if (idxs->nick_idx == -1)
+			return;
+
+		middle = name->full + idxs->nick_idx + strlen (name->nick);
+		middle = g_utf8_next_char (middle);
+		
+		while (g_unichar_isspace (g_utf8_get_char (middle)) &&
+		       *middle != '\0')
+			middle = g_utf8_next_char (middle);
+
+		if (*middle == '\0')
+			return;
+	}
+
+	/*
+	 * Make sure this isn't the beginning of a complex last name.
+	 */
+	word = e_name_western_get_words_at_idx (name->full, middle - name->full, 1);
+	if (e_name_western_is_complex_last_beginning (word)) {
+		g_free (word);
+		return;
+	}
+
+	/*
+	 * Make sure this isn't a suffix.
+	 */
+	e_name_western_cleanup_string (& word);
+	if (e_name_western_word_is_suffix (word)) {
+		g_free (word);
+		return;
+	}
+
+	/*
+	 * Make sure we didn't just grab a cute nickname.
+	 */
+	if (word [0] == '\"') {
+		g_free (word);
+		return;
+	}
+	
+	idxs->middle_idx = middle - name->full;
+	name->middle = word;
+}
+
+static void
+e_name_western_extract_nickname (ENameWestern *name, ENameWesternIdxs *idxs)
+{
+	char *nick;
+	int   start_idx;
+	GString *str;
+
+	if (idxs->first_idx == -1)
+		return;
+
+	if (idxs->middle_idx > idxs->first_idx)
+		nick = name->full + idxs->middle_idx + strlen (name->middle);
+	else
+		nick = name->full + idxs->first_idx + strlen (name->first);
+
+	while (*nick != '\"' && *nick != '\0')
+		nick = g_utf8_next_char (nick);
+
+	if (*nick != '\"')
+		return;
+
+	start_idx = nick - name->full;
+
+	/*
+	 * Advance to the next double quote.
+	 */
+	str = g_string_new ("\"");
+	nick = g_utf8_next_char (nick);
+
+	while (*nick != '\"' && *nick != '\0') {
+		str = g_string_append_unichar (str, g_utf8_get_char (nick));
+		nick = g_utf8_next_char (nick);
+	}
+
+	if (*nick == '\0') {
+		g_string_free (str, TRUE);
+		return;
+	}
+	str = g_string_append (str, "\"");
+
+	name->nick = g_string_free (str, FALSE);
+
+	idxs->nick_idx = start_idx;
+}
+
+static int
+e_name_western_last_get_max_idx (ENameWestern *name, ENameWesternIdxs *idxs)
+{
+	int max_idx = -1;
+
+	if (name->prefix != NULL)
+		max_idx = e_name_western_max (
+			max_idx, idxs->prefix_idx + strlen (name->prefix));
+
+	if (name->first != NULL)
+		max_idx = e_name_western_max (
+			max_idx, idxs->first_idx + strlen (name->first));
+
+	if (name->middle != NULL)
+		max_idx = e_name_western_max (
+			max_idx, idxs->middle_idx + strlen (name->middle));
+
+	if (name->nick != NULL)
+		max_idx = e_name_western_max (
+			max_idx, idxs->nick_idx + strlen (name->nick));
+
+	return max_idx;
+}
+
+static void
+e_name_western_extract_last (ENameWestern *name, ENameWesternIdxs *idxs)
+{
+	char *word;
+	int   idx = -1;
+	char *last;
+
+	idx = e_name_western_last_get_max_idx (name, idxs);
+
+	/*
+	 * In the case where there is no preceding name element, the
+	 * name is either just a first name ("Nat", "John"), is a
+	 * single-element name ("Cher", which we treat as a first
+	 * name), or is just a last name.  The only time we can
+	 * differentiate a last name alone from a single-element name
+	 * or a first name alone is if it's a complex last name ("de
+	 * Icaza", "van Josephsen").  So if there is no preceding name
+	 * element, we check to see whether or not the first part of
+	 * the name is the beginning of a complex name.  If it is,
+	 * we subsume the entire string.  If we accidentally subsume
+	 * the suffix, this will get fixed in the fixup routine.
+	 */
+	if (idx == -1) {
+		word = e_name_western_get_words_at_idx (name->full, 0, 1);
+		if (! e_name_western_is_complex_last_beginning (word)) {
+			g_free (word);
+			return;
+		}
+
+		name->last     = g_strdup (name->full);
+		idxs->last_idx = 0;
+		return;
+	}
+
+	last = name->full + idx;
+
+	/* Skip past the white space. */
+	while (g_unichar_isspace (g_utf8_get_char (last)) && *last != '\0')
+		last = g_utf8_next_char (last);
+
+	if (*last == '\0')
+		return;
+
+	word = e_name_western_get_words_at_idx (name->full, last - name->full, 1);
+	e_name_western_cleanup_string (& word);
+	if (e_name_western_word_is_suffix (word)) {
+		g_free (word);
+		return;
+	}
+	g_free (word);
+
+	/*
+	 * Subsume the rest of the string into the last name.  If we
+	 * accidentally include the prefix, it will get fixed later.
+	 * This is the only way to handle things like "Miguel de Icaza
+	 * Amozorrutia" without dropping data and forcing the user
+	 * to retype it.
+	 */
+	name->last = g_strdup (last);
+	idxs->last_idx = last - name->full;
+}
+
+static char *
+e_name_western_get_preceding_word (char *str, int idx)
+{
+	int   word_len;
+	char *word;
+	char *p;
+
+	p = str + idx;
+
+	while (g_unichar_isspace (g_utf8_get_char (p)) && p > str)
+		p = g_utf8_prev_char (p);
+
+	while (! g_unichar_isspace (g_utf8_get_char (p)) && p > str)
+		p = g_utf8_prev_char (p);
+
+	if (g_unichar_isspace (g_utf8_get_char (p)))
+		p = g_utf8_next_char (p);
+
+	word_len = (str + idx) - p;
+	word = g_malloc0 (word_len + 1);
+	if (word_len > 0)
+		strncpy (word, p, word_len);
+
+	return word;
+}
+
+static char *
+e_name_western_get_suffix_at_str_end (char *str)
+{
+	char *suffix;
+	char *p;
+
+	/*
+	 * Walk backwards till we reach the beginning of the
+	 * (potentially-comma-separated) list of suffixes.
+	 */
+	p = str + strlen (str);
+	while (1) {
+		char *nextp;
+		char *word;
+
+		word = e_name_western_get_preceding_word (str, p - str);
+		nextp = p - strlen (word);
+		if (nextp == str) {
+			g_free (word);
+			break;
+		}
+		nextp = g_utf8_prev_char (nextp);
+		
+		e_name_western_cleanup_string (& word);
+
+		if (e_name_western_word_is_suffix (word)) {
+			p = nextp;
+			g_free (word);
+		} else {
+			g_free (word);
+			break;
+		}
+	}
+
+	if (p == (str + strlen (str)))
+		return NULL;
+
+	suffix = g_strdup (p);
+	e_name_western_cleanup_string (& suffix);
+
+	if (strlen (suffix) == 0) {
+		g_free (suffix);
+		return NULL;
+	}
+
+	return suffix;
+}
+
+static void
+e_name_western_extract_suffix (ENameWestern *name, ENameWesternIdxs *idxs)
+{
+	name->suffix = e_name_western_get_suffix_at_str_end (name->full);
+
+	if (name->suffix == NULL)
+		return;
+
+	idxs->suffix_idx = strlen (name->full) - strlen (name->suffix);
+}
+
+static gboolean
+e_name_western_detect_backwards (ENameWestern *name, ENameWesternIdxs *idxs)
+{
+	char *comma;
+	char *word;
+
+	comma = g_utf8_strchr (name->full, -1, ',');
+
+	if (comma == NULL)
+		return FALSE;
+
+	/*
+	 * If there's a comma, we need to detect whether it's
+	 * separating the last name from the first or just separating
+	 * suffixes.  So we grab the word which comes before the
+	 * comma and check if it's a suffix.
+	 */
+	word = e_name_western_get_preceding_word (name->full, comma - name->full);
+
+	if (e_name_western_word_is_suffix (word)) {
+		g_free (word);
+		return FALSE;
+	}
+
+	g_free (word);
+	return TRUE;
+}
+
+static void
+e_name_western_reorder_asshole (ENameWestern *name, ENameWesternIdxs *idxs)
+{
+	char *prefix;
+	char *last;
+	char *suffix;
+	char *firstmidnick;
+	char *newfull;
+
+	char *comma;
+	char *p;
+
+	if (! e_name_western_detect_backwards (name, idxs))
+		return;
+
+	/*
+	 * Convert
+	 *    <Prefix> <Last name>, <First name> <Middle[+nick] name> <Suffix>
+	 * to
+	 *    <Prefix> <First name> <Middle[+nick] name> <Last name> <Suffix>
+	 */
+	
+	/*
+	 * Grab the prefix from the beginning.
+	 */
+	prefix = e_name_western_get_prefix_at_str (name->full);
+
+	/*
+	 * Everything from the end of the prefix to the comma is the
+	 * last name.
+	 */
+	comma = g_utf8_strchr (name->full, -1, ',');
+	if (comma == NULL)
+		return;
+
+	p = name->full + (prefix == NULL ? 0 : strlen (prefix));
+
+	while (g_unichar_isspace (g_utf8_get_char (p)) && *p != '\0')
+		p = g_utf8_next_char (p);
+
+	last = g_malloc0 (comma - p + 1);
+	strncpy (last, p, comma - p);
+
+	/*
+	 * Get the suffix off the end.
+	 */
+	suffix = e_name_western_get_suffix_at_str_end (name->full);
+
+	/*
+	 * Firstmidnick is everything from the comma to the beginning
+	 * of the suffix.
+	 */
+	p = g_utf8_next_char (comma);
+
+	while (g_unichar_isspace (g_utf8_get_char (p)) && *p != '\0')
+		p = g_utf8_next_char (p);
+
+	if (suffix != NULL) {
+		char *q;
+
+		/*
+		 * Point q at the beginning of the suffix.
+		 */
+		q = name->full + strlen (name->full) - strlen (suffix);
+		q = g_utf8_prev_char (q);
+
+		/*
+		 * Walk backwards until we hit the space which
+		 * separates the suffix from firstmidnick.
+		 */
+		while (! g_unichar_isspace (g_utf8_get_char (q)) && q > comma)
+			q = g_utf8_prev_char (q);
+
+		if ((q - p + 1) > 0) {
+			firstmidnick = g_malloc0 (q - p + 1);
+			strncpy (firstmidnick, p, q - p);
+		} else
+			firstmidnick = NULL;
+	} else {
+		firstmidnick = g_strdup (p);
+	}
+
+	/*
+	 * Create our new reordered version of the name.
+	 */
+#define NULLSTR(a) ((a) == NULL ? "" : (a))
+	newfull = g_strdup_printf ("%s %s %s %s", NULLSTR (prefix), NULLSTR (firstmidnick),
+				   NULLSTR (last), NULLSTR (suffix));
+	g_strstrip (newfull);
+	g_free (name->full);
+	name->full = newfull;
+
+
+	g_free (prefix);
+	g_free (firstmidnick);
+	g_free (last);
+	g_free (suffix);
+}
+
+static void
+e_name_western_zap_nil (char **str, int *idx)
+{
+	if (*str == NULL)
+		return;
+
+	if (strlen (*str) != 0)
+		return;
+
+	*idx = -1;
+	g_free (*str);
+	*str = NULL;
+}
+
+#define FINISH_CHECK_MIDDLE_NAME_FOR_CONJUNCTION			\
+	char *last_start = NULL;					\
+	if (name->last)							\
+		last_start = g_utf8_strchr (name->last, -1, ' ');	\
+	if (last_start) {						\
+		char *new_last, *new_first;				\
+									\
+		new_last = g_strdup (g_utf8_next_char (last_start));	\
+		*last_start = '\0';					\
+									\
+		idxs->last_idx += (last_start - name->last) + 1;	\
+									\
+		new_first = g_strdup_printf ("%s %s %s",		\
+					     name->first,		\
+					     name->middle,		\
+					     name->last);		\
+									\
+		g_free (name->first);					\
+		g_free (name->middle);					\
+		g_free (name->last);					\
+									\
+		name->first = new_first;				\
+		name->middle = NULL;					\
+		name->last = new_last;					\
+									\
+		idxs->middle_idx = -1;					\
+	} else {							\
+		char *new_first;					\
+									\
+		new_first = g_strdup_printf ("%s %s %s",		\
+					     name->first,		\
+					     name->middle,		\
+					     name->last);		\
+									\
+		g_free (name->first);					\
+		g_free (name->middle);					\
+		g_free (name->last);					\
+									\
+		name->first = new_first;				\
+		name->middle = NULL;					\
+		name->last = NULL;					\
+		idxs->middle_idx = -1;					\
+		idxs->last_idx = -1;					\
+	}
+
+#define CHECK_MIDDLE_NAME_FOR_CONJUNCTION(conj) \
+	if (idxs->middle_idx != -1 && !strcmp (name->middle, conj)) {	\
+		FINISH_CHECK_MIDDLE_NAME_FOR_CONJUNCTION	\
+	}
+
+#define CHECK_MIDDLE_NAME_FOR_CONJUNCTION_CASE(conj) \
+	if (idxs->middle_idx != -1 && !strcasecmp (name->middle, conj)) {	\
+		FINISH_CHECK_MIDDLE_NAME_FOR_CONJUNCTION	\
+	}
+
+static void
+e_name_western_fixup (ENameWestern *name, ENameWesternIdxs *idxs)
+{
+	/*
+	 * The middle and last names cannot be the same.
+	 */
+	if (idxs->middle_idx != -1 && idxs->middle_idx == idxs->last_idx) {
+		idxs->middle_idx = -1;
+		g_free (name->middle);
+		name->middle = NULL;
+	}
+
+	/*
+	 * If we have a middle name and no last name, then we mistook
+	 * the last name for the middle name.
+	 */
+	if (idxs->last_idx == -1 && idxs->middle_idx != -1) {
+		idxs->last_idx   = idxs->middle_idx;
+		name->last       = name->middle;
+		name->middle     = NULL;
+		idxs->middle_idx = -1;
+	}
+
+	/*
+	 * Check to see if we accidentally included the suffix in the
+	 * last name.
+	 */
+	if (idxs->suffix_idx != -1 && idxs->last_idx != -1 &&
+	    idxs->suffix_idx < (idxs->last_idx + strlen (name->last))) {
+		char *sfx;
+
+		sfx = name->last + (idxs->suffix_idx - idxs->last_idx);
+		if (sfx != NULL) {
+			char *newlast;
+			char *p;
+
+			p = sfx;
+			p = g_utf8_prev_char (p);
+			while (g_unichar_isspace (g_utf8_get_char (p)) && p > name->last)
+				p = g_utf8_prev_char (p);
+			p = g_utf8_next_char (p);
+
+			newlast = g_malloc0 (p - name->last + 1);
+			strncpy (newlast, name->last, p - name->last);
+			g_free (name->last);
+			name->last = newlast;
+		}
+	}
+
+	/*
+	 * If we have a prefix and a first name, but no last name,
+	 * then we need to assign the first name to the last name.
+	 * This way we get things like "Mr Friedman" correctly.
+	 */
+	if (idxs->first_idx != -1 && idxs->prefix_idx != -1 &&
+	    idxs->last_idx == -1) {
+		name->last      = name->first;
+		idxs->last_idx  = idxs->first_idx;
+		idxs->first_idx = -1;
+		name->first     = NULL;
+	}
+
+	if (idxs->middle_idx != -1) {
+		CHECK_MIDDLE_NAME_FOR_CONJUNCTION ("&");
+		CHECK_MIDDLE_NAME_FOR_CONJUNCTION ("*");
+		CHECK_MIDDLE_NAME_FOR_CONJUNCTION ("|");
+		CHECK_MIDDLE_NAME_FOR_CONJUNCTION ("^");
+		CHECK_MIDDLE_NAME_FOR_CONJUNCTION ("&&");
+		CHECK_MIDDLE_NAME_FOR_CONJUNCTION ("||");
+		CHECK_MIDDLE_NAME_FOR_CONJUNCTION ("+");
+		CHECK_MIDDLE_NAME_FOR_CONJUNCTION ("-");
+		CHECK_MIDDLE_NAME_FOR_CONJUNCTION_CASE ("and");
+		CHECK_MIDDLE_NAME_FOR_CONJUNCTION_CASE ("or");
+		CHECK_MIDDLE_NAME_FOR_CONJUNCTION_CASE ("plus");
+
+		/* Spanish */
+		CHECK_MIDDLE_NAME_FOR_CONJUNCTION_CASE ("y");
+
+		/* German */
+		CHECK_MIDDLE_NAME_FOR_CONJUNCTION_CASE ("und");
+
+		/* Italian */
+		CHECK_MIDDLE_NAME_FOR_CONJUNCTION_CASE ("e");
+
+		/* Czech */
+		CHECK_MIDDLE_NAME_FOR_CONJUNCTION_CASE ("a");
+
+		/* Finnish */
+		CHECK_MIDDLE_NAME_FOR_CONJUNCTION_CASE ("ja");
+
+		/* French */
+		CHECK_MIDDLE_NAME_FOR_CONJUNCTION_CASE ("et");
+
+		/* Russian */
+		CHECK_MIDDLE_NAME_FOR_CONJUNCTION ("\xd0\x98"); /* u+0418 */
+		CHECK_MIDDLE_NAME_FOR_CONJUNCTION ("\xd0\xb8"); /* u+0438 */
+	}
+
+	/*
+	 * Remove stray spaces and commas (although there don't seem
+	 * to be any in the test cases, they might show up later).
+	 */
+	e_name_western_cleanup_string (& name->prefix);
+	e_name_western_cleanup_string (& name->first);
+	e_name_western_cleanup_string (& name->middle);
+	e_name_western_cleanup_string (& name->nick);
+	e_name_western_cleanup_string (& name->last);
+	e_name_western_cleanup_string (& name->suffix);
+
+	/*
+	 * Make zero-length strings just NULL.
+	 */
+	e_name_western_zap_nil (& name->prefix, & idxs->prefix_idx);
+	e_name_western_zap_nil (& name->first,  & idxs->first_idx);
+	e_name_western_zap_nil (& name->middle, & idxs->middle_idx);
+	e_name_western_zap_nil (& name->nick,   & idxs->nick_idx);
+	e_name_western_zap_nil (& name->last,   & idxs->last_idx);
+	e_name_western_zap_nil (& name->suffix, & idxs->suffix_idx);
+}
+
+/**
+ * e_name_western_western_parse_fullname:
+ * @full_name: A string containing a western name.
+ *
+ * Parses @full_name and returns an #ENameWestern struct filled with
+ * the component parts of the name.
+ *
+ * Return value: A new #ENameWestern struct.
+ **/
+ENameWestern *
+e_name_western_parse (const char *full_name)
+{
+	ENameWesternIdxs *idxs;
+	ENameWestern *wname;
+	char *end;
+
+	if (!g_utf8_validate (full_name, -1, (const char **)&end)) {
+		g_warning ("e_name_western_parse passed invalid UTF-8 sequence");
+		*end = '\0';
+	}
+
+	wname = g_new0 (ENameWestern, 1);
+
+	wname->full = g_strdup (full_name);
+
+	idxs = g_new0 (ENameWesternIdxs, 1);
+
+	idxs->prefix_idx = -1;
+	idxs->first_idx  = -1;
+	idxs->middle_idx = -1;
+	idxs->nick_idx   = -1;
+	idxs->last_idx   = -1;
+	idxs->suffix_idx = -1;
+	
+	/*
+	 * An extremely simple algorithm.
+	 *
+	 * The goal here is to get it right 95% of the time for
+	 * Western names.
+	 *
+	 * First we check to see if this is an ass-backwards name
+	 * ("Prefix Last, First Middle Suffix").  These names really
+	 * suck (imagine "Dr von Johnson, Albert Roderick Jr"), so
+	 * we reorder them first and then parse them.
+	 *
+	 * Next, we grab the most obvious assignments for the various
+	 * parts of the name.  Once this is done, we check for stupid
+	 * errors and fix them up.
+	 */
+	e_name_western_reorder_asshole  (wname, idxs);
+
+	e_name_western_extract_prefix   (wname, idxs);
+	e_name_western_extract_first    (wname, idxs);
+	e_name_western_extract_nickname (wname, idxs);
+	e_name_western_extract_middle   (wname, idxs);
+	e_name_western_extract_last     (wname, idxs);
+	e_name_western_extract_suffix   (wname, idxs);
+
+	e_name_western_fixup            (wname, idxs);
+
+	g_free (idxs);
+
+	return wname;
+}
+
+/**
+ * e_name_western_free:
+ * @w: an #ENameWestern struct
+ *
+ * Frees the @w struct and its contents.
+ **/
+void
+e_name_western_free (ENameWestern *w)
+{
+
+	g_free (w->prefix);
+	g_free (w->first);
+	g_free (w->middle);
+	g_free (w->nick);
+	g_free (w->last);
+	g_free (w->suffix);
+	
+	g_free (w->full);
+
+	g_free (w);
+}
Index: addressbook/libebook-dbus/gen-western-table.py
===================================================================
--- addressbook/libebook-dbus/gen-western-table.py	(revision 409)
+++ addressbook/libebook-dbus/gen-western-table.py	(working copy)
@@ -1 +1,39 @@
-link ../libebook-orbit/gen-western-table.py
\ No newline at end of file
+#! /usr/bin/env python
+
+import sys
+
+var = None
+strings = []
+
+def output():
+    print "static const char %s_table[] = {" % var
+    for s in strings:
+        print "  \"%s\\0\"" % s
+    print "};"
+
+    print "static const guint %s_index[] = {" % var
+    index = 0
+    for s in strings:
+        print "  %d," % index
+        index += len(s) + 1
+    print "};\n"
+    
+(S_VAR, S_STRING) = range(0, 2)
+state = S_VAR
+
+print "/* This file is generated by gen-western-table.py. DO NOT EDIT */"
+
+for l in sys.stdin.readlines():
+    l = l.strip()
+    if l == "":
+        state = S_VAR
+        output()
+        var = None
+        strings = []
+    elif state == S_VAR:
+        var = l
+        state = S_STRING
+    elif state == S_STRING:
+        strings.append(l)
+
+output()
Index: addressbook/libebook-dbus/e-name-western.h
===================================================================
--- addressbook/libebook-dbus/e-name-western.h	(revision 409)
+++ addressbook/libebook-dbus/e-name-western.h	(working copy)
@@ -1 +1,25 @@
-link ../libebook-orbit/./e-name-western.h
\ No newline at end of file
+#ifndef __E_NAME_WESTERN_H__
+#define __E_NAME_WESTERN_H__
+
+G_BEGIN_DECLS
+
+typedef struct {
+
+	/* Public */
+	char *prefix;
+	char *first;
+	char *middle;
+	char *nick;
+	char *last;
+	char *suffix;
+
+	/* Private */
+	char *full;
+} ENameWestern;
+
+ENameWestern *e_name_western_parse (const char   *full_name);
+void          e_name_western_free  (ENameWestern *w);
+
+G_END_DECLS
+
+#endif /* ! __E_NAME_WESTERN_H__ */
Index: addressbook/libebook-dbus/e-address-western.c
===================================================================
--- addressbook/libebook-dbus/e-address-western.c	(revision 409)
+++ addressbook/libebook-dbus/e-address-western.c	(working copy)
@@ -1 +1,456 @@
-link ../libebook-orbit/./e-address-western.c
\ No newline at end of file
+/* --------------------------------------------------
+
+ An address parser, yielding fields as per RFC 2426.
+
+ Author:
+   Jesse Pavel (jpavel@ximian.com)
+
+ Copyright 2000, Ximian, Inc.
+   -------------------------------------------------- 
+*/
+
+#include <ctype.h>
+#include <string.h>
+#include <glib.h>
+
+#include "e-address-western.h"
+#include "libedataserver/e-util.h"
+
+/* These are the keywords that will distinguish the start of an extended
+   address. */
+
+static char *extended_keywords[] = {
+	"apt", "apartment", "suite", NULL
+};
+
+
+
+static gboolean
+e_address_western_is_line_blank (gchar *line)
+{
+	gboolean blank = TRUE;
+	gint cntr;
+
+	/* A blank line consists of whitespace only, or a NULL line. */
+	for (cntr = 0; line[cntr] != '\0'; cntr++ ) {
+		if (!isspace(line[cntr])) {
+			blank = FALSE;
+			break;
+		}
+	}
+
+	return blank;
+}
+
+
+
+/* In the array of lines, `lines', we will erase the line at line_num, and
+ shift the remaining lines, up to line number num_lines, up one position. */
+
+static void
+e_address_western_shift_line (gchar *lines[], gint line_num, gint num_lines)
+{
+	gint cntr;
+
+	if (line_num >= (num_lines - 1)) {
+		/* It is the last line, so simply shift in a NULL. */
+		lines[line_num] = NULL;
+	}
+	else {
+		for (cntr = line_num; cntr < num_lines; cntr++)
+			lines[cntr] = lines[cntr + 1];
+	}
+}
+
+
+static void 
+e_address_western_remove_blank_lines (gchar *lines[], gint *linecntr)
+{
+	gint cntr;
+
+	for (cntr = 0; cntr < *linecntr; cntr++) {
+		if (e_address_western_is_line_blank (lines[cntr])) {
+			/* Delete the blank line, and shift all subsequent lines up
+			   one spot to fill its old spot. */
+			e_address_western_shift_line (lines, cntr, *linecntr);
+
+			/* Since we must check the newly shifted line, let's 
+			  not advance the counter on this next pass. */
+			cntr--;
+
+			/* There is now one less line, total. */
+			*linecntr -= 1;
+		}
+	}
+}
+ 
+
+static gboolean
+e_address_western_is_po_box (gchar *line)
+{
+	gboolean retval = FALSE;
+
+	/* In which phase of processing are we? */
+	enum State { FIRSTCHAR, SECONDCHAR, WHITESPACE } state;
+	
+	
+	/* If the first two letters of the line are `p' and `o', and these
+	 are in turn followed by whitespace before another letter, then I
+	 will deem the line a representation of a PO Box address. */
+	
+	gint cntr;
+
+	state = FIRSTCHAR;
+	for (cntr = 0; line[cntr] != '\0'; cntr++) {
+		if (state == FIRSTCHAR)	{
+			if (isalnum(line[cntr])) {
+				if (tolower(line[cntr]) == 'p')
+					state = SECONDCHAR;
+				else {
+					retval = FALSE;
+					break;
+				}
+			}
+		}
+		else if (state == SECONDCHAR) {
+			if (isalnum (line[cntr])) {
+				if (tolower(line[cntr]) == 'o')
+					state = WHITESPACE;
+				else {
+					retval = FALSE;
+					break;
+				}
+			}
+		}
+		else if (state == WHITESPACE) {
+			if (isspace (line[cntr])) {
+				retval = TRUE;
+				break;
+			}
+			else if (isalnum (line[cntr])) {
+				retval = FALSE;
+				break;
+			}
+		}
+	}
+
+	return retval;
+}
+
+/* A line that contains a comma followed eventually by a number is
+  deemed to be the line in the form of <town, region postal-code>. */
+
+static gboolean
+e_address_western_is_postal (gchar *line)
+{
+	gboolean retval;
+	int cntr;
+	
+	if (strchr (line, ',') == NULL)
+		retval = FALSE;  /* No comma. */
+	else {
+		int index;
+		
+		/* Ensure that the first character after the comma is
+		 a letter. */
+		index = strcspn (line, ",");
+		index++;
+		while (isspace(line[index]))
+			index++;
+		
+		if (!isalpha (line[index]))
+			return FALSE;   /* FIXME: ugly control flow. */
+
+		cntr = strlen(line) - 1;
+
+		/* Go to the character immediately following the last
+		  whitespace character. */
+		while (cntr >= 0 && isspace(line[cntr]))
+			cntr--;	
+		
+		while (cntr >= 0 && !isspace(line[cntr]))
+			cntr--;
+
+		if (cntr == 0)
+			retval = FALSE;
+		else {
+			if (isdigit (line[cntr+1]))
+				retval = TRUE;
+			else
+				retval = FALSE;
+		}
+	}	
+
+	return retval;
+}
+
+static gchar *
+e_address_western_extract_po_box (gchar *line)
+{
+	/* Return everything from the beginning of the line to
+	   the end of the first word that contains a number. */
+	
+	int index;
+
+	index = 0;
+	while (!isdigit(line[index]))
+		index++;
+	
+	while (isgraph(line[index]))
+		index++;
+	
+	return g_strndup (line, index);
+}
+
+static gchar *
+e_address_western_extract_locality (gchar *line)
+{
+	gint index;
+
+	/* Everything before the comma is the locality. */
+	index = strcspn(line, ",");
+
+	if (index == 0)
+		return NULL;
+	else
+		return g_strndup (line, index);
+}
+
+
+/* Whatever resides between the comma and the start of the
+  postal code is deemed to be the region. */
+
+static gchar *
+e_address_western_extract_region (gchar *line)
+{
+	gint start, end, alt_end;
+
+	start = strcspn (line, ",");
+	start++;
+	while (isspace(line[start]))
+		start++;
+	
+	end = strlen(line) - 1;
+	while (end >= 0 && isspace (line[end]))
+		end--;
+
+	alt_end = end;
+
+	while (end >= 0 && !isspace (line[end]))
+		end--;
+
+	while (end >= 0 && isspace (line[end]))
+		end--;
+	end++;
+
+	if (end <= start)
+		end = alt_end;
+	if (end <= start)
+		return g_strdup ("");
+
+	/* Between start and end lie the string. */
+	return g_strndup ( (line+start), end-start);
+}
+
+static gchar *
+e_address_western_extract_postal_code (gchar *line)
+{
+	int start, end;
+
+	end = strlen (line) - 1;
+	while (end >= 0 && isspace(line[end]))
+		end--;
+	
+	start = end;
+	end++;
+
+	while (start >= 0 && !isspace(line[start]))
+		start--;
+	start++;	
+
+	/* Between start and end lie the string. */
+	return g_strndup ( (line+start), end-start);
+}
+
+static void
+e_address_western_extract_street (gchar *line, gchar **street, gchar **extended)
+{
+        const gchar *split = NULL;
+	gint cntr;
+
+	for (cntr = 0; extended_keywords[cntr] != NULL; cntr++) {
+		split = e_util_strstrcase (line, extended_keywords[cntr]);
+		if (split != NULL)
+			break;
+	}
+
+	if (split != NULL) {
+		*street = g_strndup (line, (split - line));
+		*extended = g_strdup (split);
+	}
+	else {
+		*street = g_strdup (line);
+		*extended = NULL;
+	}
+
+}
+
+/**
+ * e_address_western_parse:
+ * @in_address: a string representing a mailing address
+ *
+ * Parses a string representing a mailing address into a
+ * structure of type #EAddressWestern.
+ *
+ * Return value: A new #EAddressWestern structure, or %NULL if the parsing failed.
+ **/
+EAddressWestern *
+e_address_western_parse (const gchar *in_address)
+{
+	gchar **lines;
+	gint linecntr, lineindex;
+	gchar *address;
+	gint cntr;
+	gboolean found_po_box, found_postal;
+
+	EAddressWestern *eaw;
+#if 0
+	gint start, end;  /* To be used to classify address lines. */
+#endif
+
+	if (in_address == NULL)
+		return NULL;
+	
+	eaw = (EAddressWestern *)g_malloc (sizeof(EAddressWestern));
+	eaw->po_box = NULL;
+	eaw->extended = NULL;
+	eaw->street = NULL;
+	eaw->locality = NULL;
+	eaw->region = NULL;
+	eaw->postal_code = NULL;
+	eaw->country = NULL;
+	
+	address = g_strndup (in_address, 2047);
+
+	/* The first thing I'll do is divide the multiline input string
+	into lines. */
+
+	/* ... count the lines. */
+	linecntr = 1;
+	lineindex = 0;
+	while (address[lineindex] != '\0') {
+		if (address[lineindex] == '\n')
+			linecntr++;
+			
+		lineindex++;
+	}
+
+	/* ... tally them. */
+	lines = (gchar **)g_malloc (sizeof(gchar *) * (linecntr+3));
+	lineindex = 0;
+	lines[0] = &address[0];
+	linecntr = 1;
+	while (address[lineindex] != '\0') {
+		if (address[lineindex] == '\n') {
+			lines[linecntr] = &address[lineindex + 1];
+			linecntr++;
+		}
+
+		lineindex++;
+	}
+
+	/* Convert the newlines at the end of each line (except the last,
+	 because it is already NULL terminated) to NULLs. */
+	for (cntr = 0; cntr < (linecntr - 1); cntr++) {
+		char *p;
+		p = strchr (lines[cntr], '\n');
+		if (p)
+			*p = '\0';
+	}
+
+	e_address_western_remove_blank_lines (lines, &linecntr);
+
+	/* Let's just test these functions. */
+	found_po_box = FALSE;
+	found_postal = FALSE;
+
+   	for (cntr = 0; cntr < linecntr; cntr++)  {
+		if (e_address_western_is_po_box (lines[cntr])) {
+			if (eaw->po_box == NULL)
+				eaw->po_box = e_address_western_extract_po_box (lines[cntr]);
+			found_po_box = TRUE;
+		}
+		else if (e_address_western_is_postal (lines[cntr])) {
+			if (eaw->locality == NULL)
+				eaw->locality = e_address_western_extract_locality (lines[cntr]);
+			if (eaw->region == NULL)
+				eaw->region = e_address_western_extract_region (lines[cntr]);
+			if (eaw->postal_code == NULL)
+				eaw->postal_code = e_address_western_extract_postal_code (lines[cntr]);
+			found_postal = TRUE;
+		}
+		else {
+			if (found_postal) {
+				if (eaw->country == NULL)
+					eaw->country = g_strdup (lines[cntr]);
+				else {
+					gchar *temp;
+					temp = g_strconcat (eaw->country, "\n", lines[cntr], NULL);
+					g_free (eaw->country);
+					eaw->country = temp;
+				}
+			}
+			else {
+				if (eaw->street == NULL) {
+					e_address_western_extract_street (lines[cntr], &eaw->street,
+										&eaw->extended );
+				}
+				else {
+					gchar *temp;
+					temp = g_strdup_printf (
+						"%s\n%s",
+						eaw->extended ? eaw->extended: "",
+						lines[cntr]);
+					g_free (eaw->extended);
+					eaw->extended = temp;
+				}
+			}
+		}
+	}			
+	
+	g_free (lines);
+	g_free (address);	
+	
+	return eaw;
+}	
+
+/**
+ * e_address_western_free:
+ * @eaw: an #EAddressWestern
+ *
+ * Frees @eaw and its contents.
+ **/
+void 
+e_address_western_free (EAddressWestern *eaw)
+{
+	if (eaw == NULL)
+		return;
+	
+	if (eaw->po_box != NULL)
+		g_free(eaw->po_box);
+	if (eaw->extended != NULL)
+		g_free(eaw->extended);
+	if (eaw->street != NULL)
+		g_free(eaw->street);
+	if (eaw->locality != NULL)
+		g_free(eaw->locality);
+	if (eaw->region != NULL)
+		g_free(eaw->region);
+	if (eaw->postal_code != NULL)
+		g_free(eaw->postal_code);
+	if (eaw->country != NULL)
+		g_free(eaw->country);
+	
+	g_free (eaw);
+}
+
Index: addressbook/libedata-book-dbus/e-book-backend-summary.h
===================================================================
--- addressbook/libedata-book-dbus/e-book-backend-summary.h	(revision 409)
+++ addressbook/libedata-book-dbus/e-book-backend-summary.h	(working copy)
@@ -1 +1,77 @@
-link ../libedata-book-orbit/e-book-backend-summary.h
\ No newline at end of file
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* 
+ * pas-backend-summary.h
+ * Copyright 2000, 2001, Ximian, Inc.
+ *
+ * Authors:
+ *   Chris Toshok <toshok@ximian.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License, version 2, as published by the Free Software Foundation.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef __E_BOOK_BACKEND_SUMMARY_H__
+#define __E_BOOK_BACKEND_SUMMARY_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include <libedata-book/e-data-book-types.h>
+#include <libebook/e-contact.h>
+
+G_BEGIN_DECLS
+
+#define E_TYPE_BACKEND_SUMMARY        (e_book_backend_summary_get_type ())
+#define E_BOOK_BACKEND_SUMMARY(o)          (G_TYPE_CHECK_INSTANCE_CAST ((o), E_TYPE_BACKEND_SUMMARY, EBookBackendSummary))
+#define E_BOOK_BACKEND_SUMMARY_CLASS(k)    (G_TYPE_CHECK_CLASS_CAST((k), E_BOOK_BACKEND_TYPE, EBookBackendSummaryClass))
+#define E_IS_BACKEND_SUMMARY(o)       (G_TYPE_CHECK_INSTANCE_TYPE ((o), E_TYPE_BACKEND_SUMMARY))
+#define E_IS_BACKEND_SUMMARY_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), E_TYPE_BACKEND_SUMMARY))
+#define E_BOOK_BACKEND_SUMMARY_GET_CLASS(k) (G_TYPE_INSTANCE_GET_CLASS ((obj), E_TYPE_BACKEND_SUMMARY, EBookBackendSummaryClass))
+
+typedef struct _EBookBackendSummaryPrivate EBookBackendSummaryPrivate;
+
+struct _EBookBackendSummary{
+	GObject parent_object;
+	EBookBackendSummaryPrivate *priv;
+};
+
+struct _EBookBackendSummaryClass{
+	GObjectClass parent_class;
+};
+
+EBookBackendSummary* e_book_backend_summary_new              (const char *summary_path,
+							 int flush_timeout_millis);
+GType              e_book_backend_summary_get_type         (void);
+
+/* returns FALSE if the load fails for any reason (including that the
+   summary is out of date), TRUE if it succeeds */
+gboolean           e_book_backend_summary_load             (EBookBackendSummary *summary);
+/* returns FALSE if the save fails, TRUE if it succeeds (or isn't required due to no changes) */
+gboolean           e_book_backend_summary_save              (EBookBackendSummary *summary);
+
+void               e_book_backend_summary_add_contact       (EBookBackendSummary *summary, EContact *contact);
+void               e_book_backend_summary_remove_contact    (EBookBackendSummary *summary, const char *id);
+gboolean           e_book_backend_summary_check_contact     (EBookBackendSummary *summary, const char *id);
+
+void               e_book_backend_summary_touch             (EBookBackendSummary *summary);
+
+/* returns TRUE if the summary's mtime is >= @t. */
+gboolean           e_book_backend_summary_is_up_to_date     (EBookBackendSummary *summary, time_t t);
+
+gboolean           e_book_backend_summary_is_summary_query  (EBookBackendSummary *summary, const char *query);
+GPtrArray*         e_book_backend_summary_search            (EBookBackendSummary *summary, const char *query);
+char*              e_book_backend_summary_get_summary_vcard (EBookBackendSummary *summary, const char *id);
+
+G_END_DECLS
+
+#endif /* __E_BOOK_BACKEND_SUMMARY_H__ */
Index: addressbook/libedata-book-dbus/e-book-backend-cache.c
===================================================================
--- addressbook/libedata-book-dbus/e-book-backend-cache.c	(revision 409)
+++ addressbook/libedata-book-dbus/e-book-backend-cache.c	(working copy)
@@ -1 +1,500 @@
-link ../libedata-book-orbit/e-book-backend-cache.c
\ No newline at end of file
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* A class to cache address  book conents on local file system
+ *
+ * Copyright (C) 2004 Novell, Inc.
+ *
+ * Authors: Sivaiah Nallagatla <snallagatla@novell.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include "e-book-backend-cache.h"
+#include "e-book-backend-sexp.h"
+
+struct _EBookBackendCachePrivate {
+	char *uri;
+};
+
+/* Property IDs */
+enum {
+	PROP_0,
+	PROP_URI
+};
+
+static GObjectClass *parent_class = NULL;
+
+static char *
+get_filename_from_uri (const char *uri)
+{
+	char *mangled_uri, *filename;
+	int i;
+
+	/* mangle the URI to not contain invalid characters */
+	mangled_uri = g_strdup (uri);
+	for (i = 0; i < strlen (mangled_uri); i++) {
+		switch (mangled_uri[i]) {
+		case ':' :
+		case '/' :
+			mangled_uri[i] = '_';
+		}
+	}
+
+	/* generate the file name */
+	filename = g_build_filename (g_get_home_dir (), ".evolution/cache/addressbook",
+				     mangled_uri, "cache.xml", NULL);
+
+	/* free memory */
+	g_free (mangled_uri);
+
+	return filename;
+}
+
+static void
+e_book_backend_cache_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
+{
+	EBookBackendCache *cache;
+	EBookBackendCachePrivate *priv;
+	char *cache_file;
+
+	cache = E_BOOK_BACKEND_CACHE (object);
+	priv = cache->priv;
+
+	switch (property_id) {
+	case PROP_URI :
+		cache_file = get_filename_from_uri (g_value_get_string (value));
+		if (!cache_file)
+			break;
+
+		g_object_set (G_OBJECT (cache), "filename", cache_file, NULL);
+		g_free (cache_file);
+
+		if (priv->uri)
+			g_free (priv->uri);
+		priv->uri = g_value_dup_string (value);
+		break;
+	default :
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+	}
+}
+
+static void
+e_book_backend_cache_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
+{
+	EBookBackendCache *cache;
+	EBookBackendCachePrivate *priv;
+
+	cache = E_BOOK_BACKEND_CACHE (object);
+	priv = cache->priv;
+
+	switch (property_id) {
+	case PROP_URI :
+		g_value_set_string (value, priv->uri);
+		break;
+	default :
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+	}
+}
+
+
+static void
+e_book_backend_cache_finalize (GObject *object)
+{
+	EBookBackendCache *cache;
+	EBookBackendCachePrivate *priv;
+
+	cache = E_BOOK_BACKEND_CACHE (object);
+	priv = cache->priv;
+
+	if (priv) {
+		if (priv->uri) {
+			g_free (priv->uri);
+			priv->uri = NULL;
+		}
+	
+
+		g_free (priv);
+		cache->priv = NULL;
+	}
+
+	parent_class->finalize (object);
+}
+
+static GObject *
+e_book_backend_cache_constructor (GType type,
+                                 guint n_construct_properties,
+                                 GObjectConstructParam *construct_properties)
+{
+	GObject *obj;
+	const char *uri;
+	char *cache_file;
+	EBookBackendCacheClass *klass;
+	GObjectClass *parent_class;
+
+	/* Invoke parent constructor. */
+	klass = E_BOOK_BACKEND_CACHE_CLASS (g_type_class_peek (E_TYPE_BOOK_BACKEND_CACHE));
+	parent_class = G_OBJECT_CLASS (g_type_class_peek_parent (klass));
+	obj = parent_class->constructor (type,
+					 n_construct_properties,
+					 construct_properties);
+  
+	/* extract uid */
+	if (!g_ascii_strcasecmp ( g_param_spec_get_name (construct_properties->pspec), "uri")) {
+		uri = g_value_get_string (construct_properties->value);
+		cache_file = get_filename_from_uri (uri);
+		if (cache_file)
+			g_object_set (obj, "filename", cache_file, NULL);
+		g_free (cache_file);
+	}
+
+	return obj;
+}
+
+static void
+e_book_backend_cache_class_init (EBookBackendCacheClass *klass)
+{
+	GObjectClass *object_class;
+
+	parent_class = g_type_class_peek_parent (klass);
+
+	object_class = G_OBJECT_CLASS (klass);
+	object_class->finalize = e_book_backend_cache_finalize;
+	object_class->set_property = e_book_backend_cache_set_property;
+	object_class->get_property = e_book_backend_cache_get_property;
+
+        object_class->constructor = e_book_backend_cache_constructor;
+	g_object_class_install_property (object_class, PROP_URI,
+					 g_param_spec_string ("uri", NULL, NULL, "",
+							      G_PARAM_READABLE | G_PARAM_WRITABLE
+							      | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+e_book_backend_cache_init (EBookBackendCache *cache)
+{
+	EBookBackendCachePrivate *priv;
+
+	priv = g_new0 (EBookBackendCachePrivate, 1);
+
+	cache->priv = priv;
+
+}
+
+
+GType
+e_book_backend_cache_get_type (void)
+{
+	static GType type = 0;
+
+	if (!type) {
+		static GTypeInfo info = {
+                        sizeof (EBookBackendCacheClass),
+                        (GBaseInitFunc) NULL,
+                        (GBaseFinalizeFunc) NULL,
+                        (GClassInitFunc) e_book_backend_cache_class_init,
+                        NULL, NULL,
+                        sizeof (EBookBackendCache),
+                        0,
+                        (GInstanceInitFunc) e_book_backend_cache_init,
+                };
+		type = g_type_register_static (E_TYPE_FILE_CACHE, "EBookBackendCache", &info, 0);
+	}
+
+	return type;
+}
+
+/**
+ * e_book_backend_cache_new
+ * @uri: URI of the backend to be cached.
+ *
+ * Creates a new #EBookBackendCache object, which implements a local
+ * cache of #EContact objects, useful for remote backends.
+ *
+ * Return value: A new #EBookBackendCache.
+ */
+EBookBackendCache *
+e_book_backend_cache_new (const char *uri)
+{
+	EBookBackendCache *cache;
+        
+       	cache = g_object_new (E_TYPE_BOOK_BACKEND_CACHE, "uri", uri, NULL);
+
+        return cache;
+}
+
+/**
+ * e_book_backend_cache_get_contact:
+ * @cache: an #EBookBackendCache
+ * @uid: a unique contact ID
+ *
+ * Get a cached contact. Note that the returned #EContact will be
+ * newly created, and must be unreffed by the caller when no longer
+ * needed.
+ *
+ * Return value: A cached #EContact, or %NULL if @uid is not cached.
+ **/
+EContact *
+e_book_backend_cache_get_contact (EBookBackendCache *cache, const char *uid)
+{
+	const char *vcard_str;
+	EContact *contact = NULL;
+
+	g_return_val_if_fail (E_IS_BOOK_BACKEND_CACHE (cache), NULL);
+	g_return_val_if_fail (uid != NULL, NULL);
+
+	vcard_str = e_file_cache_get_object (E_FILE_CACHE (cache), uid);
+	if (vcard_str) {
+		contact = e_contact_new_from_vcard (vcard_str);
+		
+	}
+
+
+	return contact;
+}
+
+/**
+ * e_book_backend_cache_add_contact:
+ * @cache: an #EBookBackendCache
+ * @contact: an #EContact
+ *
+ * Adds @contact to @cache.
+ *
+ * Return value: %TRUE if the contact was cached successfully, %FALSE otherwise.
+ **/
+gboolean
+e_book_backend_cache_add_contact (EBookBackendCache *cache,
+				   EContact *contact)
+{
+	char *vcard_str;
+	const char *uid;
+	gboolean retval;
+	EBookBackendCachePrivate *priv;
+
+	g_return_val_if_fail (E_IS_BOOK_BACKEND_CACHE (cache), FALSE);
+
+
+	priv = cache->priv;
+
+	uid = e_contact_get_const (contact, E_CONTACT_UID);
+	vcard_str = e_vcard_to_string (E_VCARD(contact), EVC_FORMAT_VCARD_30);
+
+	if (e_file_cache_get_object (E_FILE_CACHE (cache), uid))
+		retval = e_file_cache_replace_object (E_FILE_CACHE (cache), uid, vcard_str);
+	else
+		retval = e_file_cache_add_object (E_FILE_CACHE (cache), uid, vcard_str);
+
+	g_free (vcard_str);
+
+	return retval;
+}
+
+/**
+ * e_book_backend_cache_remove_contact:
+ * @cache: an #EBookBackendCache
+ * @uid: a unique contact ID
+ *
+ * Removes the contact identified by @uid from @cache.
+ *
+ * Return value: %TRUE if the contact was found and removed, %FALSE otherwise.
+ **/
+gboolean
+e_book_backend_cache_remove_contact (EBookBackendCache *cache,
+				    const char *uid)
+				      
+{
+	gboolean retval;
+	EBookBackendCachePrivate *priv;
+
+	g_return_val_if_fail (E_IS_BOOK_BACKEND_CACHE (cache), FALSE);
+	g_return_val_if_fail (uid != NULL, FALSE);
+
+	priv = cache->priv;
+
+
+	if (!e_file_cache_get_object (E_FILE_CACHE (cache), uid)) {
+		return FALSE;
+	}
+
+	retval = e_file_cache_remove_object (E_FILE_CACHE (cache), uid);
+
+
+	return retval;
+}
+
+/**
+ * e_book_backend_cache_check_contact:
+ * @cache: an #EBookBackendCache
+ * @uid: a unique contact ID
+ *
+ * Checks if the contact identified by @uid exists in @cache.
+ *
+ * Return value: %TRUE if the cache contains the contact, %FALSE otherwise.
+ **/
+gboolean 
+e_book_backend_cache_check_contact (EBookBackendCache *cache, const char *uid)
+{
+
+	gboolean retval;
+	EBookBackendCachePrivate *priv;
+
+	g_return_val_if_fail (E_IS_BOOK_BACKEND_CACHE (cache), FALSE);
+	g_return_val_if_fail (uid != NULL, FALSE);
+
+	priv = cache->priv;
+
+	retval = FALSE;
+	if (e_file_cache_get_object (E_FILE_CACHE (cache), uid)) 
+		retval = TRUE;
+	return retval;
+}
+
+/**
+ * e_book_backend_cache_get_contacts:
+ * @cache: an #EBookBackendCache
+ * @query: an s-expression
+ *
+ * Returns a list of #EContact elements from @cache matching @query.
+ * When done with the list, the caller must unref the contacts and
+ * free the list.
+ *
+ * Return value: A #GList of pointers to #EContact.
+ **/
+GList *
+e_book_backend_cache_get_contacts (EBookBackendCache *cache, const char *query)
+{
+        char *vcard_str;
+        GSList *l;
+	GList *list = NULL;
+	EContact *contact;
+        EBookBackendSExp *sexp = NULL;
+	const char *uid;
+
+	g_return_val_if_fail (E_IS_BOOK_BACKEND_CACHE (cache), NULL);
+	if (query) {
+		sexp = e_book_backend_sexp_new (query);
+		if (!sexp)
+			return NULL;
+	}
+       
+
+        l = e_file_cache_get_objects (E_FILE_CACHE (cache));
+
+        for ( ; l != NULL; l = g_slist_next (l)) {
+                vcard_str = l->data;
+                if (vcard_str && !strncmp (vcard_str, "BEGIN:VCARD", 11)) {
+                        contact = e_contact_new_from_vcard (vcard_str);
+			uid = e_contact_get_const (contact, E_CONTACT_UID);
+                        if (contact && uid && *uid &&(query && e_book_backend_sexp_match_contact(sexp, contact)))
+				list = g_list_prepend (list, contact);
+                }
+                
+        }
+	if (l) {
+		g_slist_foreach (l, (GFunc) g_object_unref, NULL);
+		g_slist_free (l);
+	}
+	if (sexp)
+		g_object_unref (sexp);
+
+        return g_list_reverse (list);
+}
+
+/**
+ * e_book_backend_cache_search:
+ * @cache: an #EBookBackendCache
+ * @query: an s-expression
+ *
+ * Returns an array of pointers to unique contact ID strings for contacts
+ * in @cache matching @query. When done with the array, the caller must
+ * free the ID strings and the array.
+ *
+ * Return value: A #GPtrArray of pointers to contact ID strings.
+ **/
+GPtrArray *
+e_book_backend_cache_search (EBookBackendCache *cache, const char *query)
+{
+	GList *matching_contacts, *temp;
+	GPtrArray *ptr_array;
+	
+	matching_contacts = e_book_backend_cache_get_contacts (cache, query);
+	ptr_array = g_ptr_array_new ();
+	
+	temp = matching_contacts;
+	for (; matching_contacts != NULL; matching_contacts = g_list_next (matching_contacts)) {
+		g_ptr_array_add (ptr_array, e_contact_get (matching_contacts->data, E_CONTACT_UID));
+		g_object_unref (matching_contacts->data);
+	}
+	g_list_free (temp);
+	
+	return ptr_array;
+}
+
+/**
+ * e_book_backend_cache_exists:
+ * @uri: URI for the cache
+ *
+ * Checks if an #EBookBackendCache exists at @uri.
+ *
+ * Return value: %TRUE if cache exists, %FALSE if not.
+ **/
+gboolean 
+e_book_backend_cache_exists (const char *uri)
+{
+	char *file_name;
+	gboolean exists = FALSE;
+	file_name = get_filename_from_uri (uri);
+	
+	if (file_name && g_file_test (file_name, G_FILE_TEST_EXISTS)) {
+		exists = TRUE;
+		g_free (file_name);
+	}
+	
+	return exists;
+}
+
+/**
+ * e_book_backend_cache_set_populated:
+ * @cache: an #EBookBackendCache
+ *
+ * Flags @cache as being populated - that is, it is up-to-date on the 
+ * contents of the book it's caching.
+ **/
+void
+e_book_backend_cache_set_populated (EBookBackendCache *cache)
+{
+  	g_return_if_fail (E_IS_BOOK_BACKEND_CACHE (cache));
+	e_file_cache_add_object (E_FILE_CACHE (cache), "populated", "TRUE");
+	
+}
+
+/**
+ * e_book_backend_cache_is_populated:
+ * @cache: an #EBookBackendCache
+ *
+ * Checks if @cache is populated.
+ *
+ * Return value: %TRUE if @cache is populated, %FALSE otherwise.
+ **/
+gboolean
+e_book_backend_cache_is_populated (EBookBackendCache *cache)
+{
+  	g_return_val_if_fail (E_IS_BOOK_BACKEND_CACHE (cache), FALSE);
+	if (e_file_cache_get_object (E_FILE_CACHE (cache), "populated"))
+		return TRUE;
+	return FALSE;	
+}
Index: addressbook/libedata-book-dbus/e-book-backend-cache.h
===================================================================
--- addressbook/libedata-book-dbus/e-book-backend-cache.h	(revision 409)
+++ addressbook/libedata-book-dbus/e-book-backend-cache.h	(working copy)
@@ -1 +1,67 @@
-link ../libedata-book-orbit/e-book-backend-cache.h
\ No newline at end of file
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* 
+ *  A class to cache address book conents on local file system
+ * 
+ * Copyright (C) 2004 Novell, Inc.
+ *
+ * Authors: Sivaiah Nallagatla <snallagatla@ximian.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef E_BOOK_BACKEND_CACHE_H
+#define E_BOOK_BACKEND_CACHE_H
+
+#include <libedataserver/e-file-cache.h>
+#include <libebook/e-contact.h>
+
+G_BEGIN_DECLS
+
+#define E_TYPE_BOOK_BACKEND_CACHE            (e_book_backend_cache_get_type ())
+#define E_BOOK_BACKEND_CACHE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), E_TYPE_BOOK_BACKEND_CACHE, EBookBackendCache))
+#define E_BOOK_BACKEND_CACHE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), E_TYPE_BOOK_BACKEND_CACHE, EBookBackendCacheClass))
+#define E_IS_BOOK_BACKEND_CACHE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), E_TYPE_BOOK_BACKEND_CACHE))
+#define E_IS_BOOK_BACKEND_CACHE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), E_TYPE_BOOK_BACKEND_CACHE))
+
+typedef struct _EBookBackendCachePrivate EBookBackendCachePrivate;
+
+typedef struct {
+	EFileCache parent;
+	EBookBackendCachePrivate *priv;
+} EBookBackendCache;
+
+typedef struct {
+	EFileCacheClass parent_class;
+} EBookBackendCacheClass;
+
+GType e_book_backend_cache_get_type (void);
+EBookBackendCache* e_book_backend_cache_new (const char *uri);
+EContact* e_book_backend_cache_get_contact (EBookBackendCache *cache, const char *uid);
+gboolean e_book_backend_cache_add_contact (EBookBackendCache *cache,
+					   EContact *contact);
+gboolean e_book_backend_cache_remove_contact (EBookBackendCache *cache,
+					      const char *uid);
+gboolean e_book_backend_cache_check_contact (EBookBackendCache *cache, const char *uid);
+GList*   e_book_backend_cache_get_contacts (EBookBackendCache *cache, const char *query);
+gboolean e_book_backend_cache_exists (const char *uri);
+void     e_book_backend_cache_set_populated (EBookBackendCache *cache);
+gboolean e_book_backend_cache_is_populated (EBookBackendCache *cache);
+GPtrArray* e_book_backend_cache_search (EBookBackendCache *cache, const char *query);
+
+
+
+
+G_END_DECLS
+
+#endif
Index: addressbook/libedata-book-dbus/e-book-backend-factory.h
===================================================================
--- addressbook/libedata-book-dbus/e-book-backend-factory.h	(revision 409)
+++ addressbook/libedata-book-dbus/e-book-backend-factory.h	(working copy)
@@ -1 +1,118 @@
-link ../libedata-book-orbit/e-book-backend-factory.h
\ No newline at end of file
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* e-book-backend-factory.h
+ *
+ * Copyright (C) 2004  Novell, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Chris Toshok <toshok@ximian.com>
+ */
+
+#ifndef _E_BOOK_BACKEND_FACTORY_H_
+#define _E_BOOK_BACKEND_FACTORY_H_
+
+#include <glib-object.h>
+#include "e-book-backend.h"
+
+G_BEGIN_DECLS
+
+#define E_TYPE_BOOK_BACKEND_FACTORY        (e_book_backend_factory_get_type ())
+#define E_BOOK_BACKEND_FACTORY(o)          (G_TYPE_CHECK_INSTANCE_CAST ((o), E_TYPE_BOOK_BACKEND_FACTORY, EBookBackendFactory))
+#define E_BOOK_BACKEND_FACTORY_CLASS(k)    (G_TYPE_CHECK_CLASS_CAST((k), E_TYPE_BOOK_BACKEND_FACTORY, EBookBackendFactoryClass))
+#define E_IS_BOOK_BACKEND_FACTORY(o)       (G_TYPE_CHECK_INSTANCE_TYPE ((o), E_TYPE_BOOK_BACKEND_FACTORY))
+#define E_IS_BOOK_BACKEND_FACTORY_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), E_TYPE_BOOK_BACKEND_FACTORY))
+#define E_BOOK_BACKEND_FACTORY_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), E_TYPE_BOOK_BACKEND_FACTORY, EBookBackendFactoryClass))
+
+typedef struct _EBookBackendFactoryPrivate EBookBackendFactoryPrivate;
+
+typedef struct {
+	GObject            parent_object;
+} EBookBackendFactory;
+
+typedef struct {
+	GObjectClass parent_class;
+
+	const char*   (*get_protocol) (EBookBackendFactory *factory);
+	EBookBackend* (*new_backend)  (EBookBackendFactory *factory);
+} EBookBackendFactoryClass;
+
+GType                e_book_backend_factory_get_type             (void);
+
+const char*          e_book_backend_factory_get_protocol         (EBookBackendFactory *factory);
+EBookBackend*        e_book_backend_factory_new_backend          (EBookBackendFactory *factory);
+
+
+/* use this macro for simple, 1 factory modules */
+#define E_BOOK_BACKEND_FACTORY_SIMPLE(p,t,f) \
+typedef struct { \
+	EBookBackendFactory      parent_object; \
+} EBookBackend##t##Factory; \
+\
+typedef struct { \
+	EBookBackendFactoryClass parent_class; \
+} EBookBackend##t##FactoryClass; \
+\
+static void \
+_ ## p ##_factory_instance_init (EBookBackend## t ##Factory *factory) \
+{ \
+} \
+\
+static const char * \
+_ ## p ##_get_protocol (EBookBackendFactory *factory) \
+{ \
+	return #p; \
+} \
+\
+static EBookBackend* \
+_ ## p ##_new_backend (EBookBackendFactory *factory) \
+{ \
+	return (f) (); \
+} \
+\
+static void \
+_ ## p ##_factory_class_init (EBookBackend## t ##FactoryClass *klass) \
+{ \
+	E_BOOK_BACKEND_FACTORY_CLASS (klass)->get_protocol = _ ## p ##_get_protocol; \
+	E_BOOK_BACKEND_FACTORY_CLASS (klass)->new_backend = _ ## p ##_new_backend; \
+} \
+\
+static GType \
+_ ## p ##_factory_get_type (GTypeModule *module) \
+{ \
+	GType type; \
+\
+	GTypeInfo info = { \
+		sizeof (EBookBackend##t##FactoryClass), \
+		NULL, /* base_class_init */ \
+		NULL, /* base_class_finalize */ \
+		(GClassInitFunc)  _ ## p ##_factory_class_init, \
+		NULL, /* class_finalize */ \
+		NULL, /* class_data */ \
+		sizeof (EBookBackend##t##Factory), \
+		0,    /* n_preallocs */ \
+		(GInstanceInitFunc) _ ## p ##_factory_instance_init \
+	}; \
+\
+	type = g_type_module_register_type (module, \
+					    E_TYPE_BOOK_BACKEND_FACTORY, \
+					    "EBookBackend" #t "Factory", \
+					    &info, 0); \
+\
+	return type; \
+}
+
+G_END_DECLS
+
+#endif /* _E_BOOK_BACKEND_FACTORY_H_ */
Index: addressbook/libedata-book-dbus/ximian-vcard.h
===================================================================
--- addressbook/libedata-book-dbus/ximian-vcard.h	(revision 409)
+++ addressbook/libedata-book-dbus/ximian-vcard.h	(working copy)
@@ -1 +1,80 @@
-link ../libedata-book-orbit/ximian-vcard.h
\ No newline at end of file
+#define XIMIAN_VCARD \
+"BEGIN:VCARD\n" \
+"X-EVOLUTION-FILE-AS:Novell Ximian Group\n" \
+"ADR;TYPE=WORK:;Suite 500;8 Cambridge Center;Cambridge;MA;02142;USA\n" \
+"LABEL;TYPE=WORK:8 Cambridge Center, Suite 500\\nCambridge\\, MA\\n02142\\nUSA\n" \
+"TEL;WORK;VOICE:(617) 613-2000\n" \
+"TEL;WORK;FAX:(617) 613-2001\n" \
+"EMAIL;INTERNET:hello@ximian.com\n" \
+"URL:http://www.ximian.com/\n" \
+"ORG:Novell;Ximian Group\n" \
+"PHOTO;ENCODING=b;TYPE=JPEG:/9j/4AAQSkZJRgABAQEARwBHAAD//gAXQ3JlYXRlZCB3aXRo\n" \
+" IFRoZSBHSU1Q/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCM\n" \
+" cHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMj\n" \
+" IyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAbgBkAwEiAAIRAQMRAf/EA\n" \
+" BwAAAIDAQEBAQAAAAAAAAAAAAAHBQYIBAMBAv/EAEYQAAEDAwEFBgMEBgQPAAAAAAECAwQABREG\n" \
+" BxIhMWETIkFRcYEUkaEIMkLBFSNSsbLRFmJydRgkMzY3Q0RGgpKTosLh8P/EABsBAQACAwEBAAA\n" \
+" AAAAAAAAAAAAEBQIDBgEH/8QALREAAQMCAwYGAgMAAAAAAAAAAQACAwQREiFRBRMiMUFhMnGBkb\n" \
+" HRBsEUofD/2gAMAwEAAhEDEQA/AH/RRRREVwXe9W2wwFzbpNZixkc1uqwPQeZ6CoHXevLfom1ds\n" \
+" 9h6a6D8PGCsFZHMk+CR4n86yzdbrqfaZqYBSnp0hRPZMoG62ynoOSR1Pua8Lg0XPJegX5Jv6k+0\n" \
+" bBjrWxp22LlkcBIlHs0HqEjiR64peT9umupqyWrhHhpP4WI6eHureNW7Tmw+DGaTI1FJVJdxksM\n" \
+" qKG09CrmfbFMCHpCw2xATDs8JrH4gykq+Z4mqifbMUZsxpd/QUllK53M2SCb2xa+bXvf0gcV0Uw\n" \
+" 0R/DVktH2hdUwlpFxjQrg1490tLPuOH0pvv2qE4jdchx1p8lNAj91Va87OtM3RCt+2Nx3Dyci/q\n" \
+" yPYcD7g1EZ+RR4rSMI9b/S2mhdbhKsmkdtWmNTuNxnXVW2cvgGZRASo+SV8j74PSmOlQUMpORWP\n" \
+" NU7MrjY0rlQFmdDTxOE4cQOo8R1Hyqe2Z7ZJ2m32bXfHnJVpJCUuqO8uP7+Kenh4eVXkFRFUMxx\n" \
+" G4UOSN0Zs4LU1FeEOWxOityYzqHWXEhSFoOQoHkQa963rBFFFFERUdfr1E09Y5d1mr3Y8ZsrV5n\n" \
+" yA6k4A6mpGkL9ojUym0W/TrLmAsGU+AeYBwgfPJ/4RREqrrcb1tJ1oUpBXLmObqUZ7rSByT0SkZ\n" \
+" J8zk1pHQmiLXo+zpbabC3SAp55Q7zyvM9PIUudiGmURbS7fpCMvzFFton8LSTxx6qH0FM7VV9VY\n" \
+" 9MzZ7aQt5tASw3+26ohKB/zEVSVFVvZzGMw02tqe/kpbI8LMR6/C/Xxq9QagfbbP+IW1QQ4Rycf\n" \
+" xncHRAIJ/rEfsmu2a9Fgsl2XIZjtj8bqwgfM1+9L2VFksESAV9o6hG886ebjqjvLWepUSarutdn\n" \
+" MXV+obRcZks/CwCQ5DKMpeBOTxzwzgA9KwfTtfxPOSB5GQUXc9pOjoC+zXe2HV5xiOC6PmkEfWp\n" \
+" xe6tAWghSVDIIOQRXxekNOx4b0WPZYLLTram19mwlJKSMHjjNUzQd2dZM7SNxczcLOsttqVzdYz\n" \
+" 3FewI9iKpK2mjMZdFe7ed9NfT9qZDI4OAd1Vkko50ndoui22kuXq2NBOO9JZSOH9sD9/z86c8gc\n" \
+" DUJNQlaFJUkKSoYII4EVGoKp9PIHt9e6lyRNlZhcqlsJ2guQpydL3F4mO7kw1KP3Fcyj0PEjrnz\n" \
+" rSAIIyOVYfvsJ3TGqlCKpTfYuJfjLHMDOR8jw9q2Foy+o1FpWBckY/XMpUoeRxxHsciu/jeJGB7\n" \
+" eRXPvaWuLT0U/RRRWaxQeVY82x3BVw2oXbJyhgoZR0AQM/UmthK+6fSsWbRQW9pV73x/tZPtwNE\n" \
+" Wj9Nw0WuwwIKQAGI6G/cAZ+tRW0lx5nTEW4Ntqdat9xjy5CEjJLSFZP5H2qaYdCkpUk5BGQa7Ap\n" \
+" DrSm3EpWhYKVJUMgg8wRXz+kqyyTG7VXUsV22Clrfc48+CzMiPIejvIC23EHIUDXNe79b7HbXbh\n" \
+" c5SI8ZvmtZ5nyA5k9BS7d0nfdMPuSdD3JtEZaitdom5Uznx3DzT6cPWkvq/V1611fGW5nZtBCgy\n" \
+" zFbXhtCycE5JxknxPhXR07RUeB3D11H+9lAfwcxmrrqLbxcHpikWGAw1FScByUkqWvrgEBPpxqi\n" \
+" ztdXWdqmNqIIjx7gykJUphJCXAM/eBJ5g4PQCmBZNiDKWEu364uF0jJYh4AT6qUDn2FVu6bPIkT\n" \
+" aTB08xKeMOU2H99eCtKRvZGQMZ7hwceNZxVGzsbmMzIBv5dfNeOjnsCdUwbTtKsV8nJgIccZkqw\n" \
+" lJcThDqvJJz8s4zUtLVzpc2vZZKt+qBIkyUKt0V0ONKSe+7g5SCPDr9Kv0tznXP1cNMyQfxnXBC\n" \
+" tqUyuB3gslftPjJLkGWB3u82o/Ij86bf2e7iqRoxyIpWfhpC0JHQ4V/5GlVtJcBt0RPiXif+00w\n" \
+" Ps5BQtNxP4TJP8Ka6rZZJpW37/Kq68ATlPeiiirBQ0HlWR9t9qVbtpEp/dwiY0h5J8Mgbp/h+ta\n" \
+" 4pM7fdKLumn2rxGbKn4BKl4HEtn73ywD7GiL7o28JuulLbKCsqLKUL/tJ7p+oqyIe4c6RGyzU4g\n" \
+" THLNJc3WpCt9gk8A54j3GPcdaZuoosy82V23QpaYpkEIdeIJKUeIAHieXPkTXA11DuassJsCefY\n" \
+" /SvYZN5FiGZU9edRwLDAXJny2mRukoStQBWQOQHjSjg7PYE7ZmzcZb7cG6KK5CZD6txOCcJQvPg\n" \
+" QAQfAn2q6RNOWi1D9J3R5dwlR2xmZPVv9mlI8ByTj59ar09Lm0jUIQl5Y0zAUMrQSPiXfHHpyz4\n" \
+" D1qTRvMQIieQAQXOtllfIDre/X2WqVmI8Qz6D9q0bP9SO37SrSpW8ZUVXw7q+YcKeSgeRyMZ65q\n" \
+" qammvWTalEv1yjOJtaWfh25CBvBOUkHPlxUeHlyq/MiPCitxorSGWG07qG0DASK45xZlx3GJDaH\n" \
+" WljCkLGQR6VGinY2ofIG8Lri2gOi37hxYG3zC+uT2HY6ZDbyFMrAUlwK7pB5HNRcp7nxqpzdN3G\n" \
+" CFQ7NObTa3nApcaSN/suOe4SDw6VK3O4swojsp9WGmxk9fIDrW4UzWkbt2K/v691vjec8YtZUTa\n" \
+" BL+IuMaIjiWWytXQn/wBD608tgtrVC0W2+tOFSFqd9icD6AVnmFFl6n1AhoAmRPdwcfgR4n2H7q\n" \
+" 2Ppi1N2exRojaQlKEBIHkAK7Gmi3MTWaLn6iTeSF+qmaKKK3rSiuedEanQ3I7qQpC0kEEZzXRRR\n" \
+" FjnaRoSVoq/KcYQv9HOr3mHB/qzz3SenhVi0ftAbnNNwLo6G5iQEodUcJd9fJX760ZqLTkHUdsd\n" \
+" hTWEOtuJwQoVl/XGyS7aakOPwGnJcDORujK0DqPH2qJV0cdUzC/0Oi3QTuhddqY84IuFukwnFFK\n" \
+" JDSmlEcwFDGR86ISI1tgtQ4jYaYaTuoSP/udJS1azvFoAZLnbsp4dm/klPQHmKs0faVEWkfEw32\n" \
+" 1f1CFj8q56XZNSwYG5t7fSt46yB5ucimM5L4c643pXWqU5tCteMpRKUfIIH86ipmvnnAUwoQSf2\n" \
+" 3lZ+g/nWEey5yfCtrquBo8Su0+4sQ46pEp1LTSeZUfoPOlnfr67fZKQlK0QkK/VtficV5nrXOkX\n" \
+" XUk9KQHp0gnghI7qPyAp1bOdkCmH2rneQHHxxQjHdb9OvWr2j2c2Didm74VZVVplGFuQXRsc2fO\n" \
+" Qgb1cmsSXQN1JH+TT4D+dPEAAADkK848duMylppISkDGBXrVkoCKKKKIiiqrrbX9m0JARIua1re\n" \
+" dJDMdoArcI58+AA8zVLsO26RqiS9Gsukpct5lHaKbTLaSrd8wFEZ9s0RN6vGRGZktlDqAoHzFKq\n" \
+" JtomzrPOuzGjZvwEBRTJfckttpbUOae9jJ5cBk8R514Wrbo7e489+3aTlvtQGTIkqElsdm2Mkq4\n" \
+" 4zyPKiKf1Hsj09flKdXEQh4/jR3VfMUvJ/2et1ZMOe8keSgFfyqz2LbfJ1M9IZs2kJsx2O0XnEN\n" \
+" yEAhA4ZwcZ58hxr7ZdtkvUS5SbTo2fJMRsuPkPoSG0jzKsDPPhz4HyoipDewC47+FXFWOjYH51Y\n" \
+" bTsAgtrSqc88/jwWrA+QxUlYtujupZ6oNo0nLlSUtqdKEyW04SMZOVYHiKjP8ACUt5/wB3pX/XT\n" \
+" /KiJnWLQ1nsTSURorad39lIFWZKUoThIAHSlNqDbLP0siKu96MnQ0ygSyVyGzvYxnlnB4jga87F\n" \
+" ttlamXJbs2j50xcZvtXUtyEZCfPB5+gyaIm9RSetm3J68RbhJgaSmPM25vtZaviW09knjxIOM8j\n" \
+" y8q7LHtzstwv/AOhrlBftkkudkFOLS43v5xgqSeHHx5daImrRX5QtK0hSTkGiiLMP2ho8wa1iSn\n" \
+" QoxVRQ20fAKClFQ9eIqq7LLJe7vreG7ZZCoZhqD8iZjustjnnwORkY8c+Wa1ZqbStt1PBMa4MId\n" \
+" Rz7wzg+dL8bEbA1vpa7RtK+CkpdWAfXjRFB7UpCNe6Kdm6NnJft1qluKuUJlvdKznPbYH3hzPXJ\n" \
+" PMGqZsk/zc2gf3G5/Cumc3sRsTO92Rcb3uB3XVjP1r4jYfYGwoN76QsYUEurGR5HjREudhUt2BP\n" \
+" 1TMYID0eyuuoJGRvJII+oq96I2iwtVz7rb7ZZWbalyzyJ9wKUjLsrKEkjH4cE8+Jz049bew+wNb\n" \
+" 3Z76N4YO66sZHlzob2H2Bkktb6CRglLqxkeXOiJZbAv9IMj+7X/wB6ag9lGnEaj17CRJA+BhZmy\n" \
+" lK+6EI44PQq3R6E06W9h9gZVvNb6FYxlLqwcfOhvYhYWt7s99G8MK3XVjI68aIo7UxgbR9IajhQ\n" \
+" 7/Du9yiSF3S3tMNrStlkAAt94DPDI4eJFUvYfNetqNYz4xAfjWZx5skZAUnJHD1FMVrYhYWVbzW\n" \
+" +2ojGUOrBx86EbD7A0FBvfRvDCt11YyPI8aIo23zdP6i2e621TaUJiXCfa1IucFPJt5KVnfHRWS\n" \
+" euPPNZ2YadfkNsspUp1aglCU8yTyrTSNh9gbCgjfSFjCgl1YyPI8al9PbItP2WamUywkuJ5KOVE\n" \
+" emeVEVw02ZH9H4YkEqdDYCifE4oqXbaS02lCRhIGBRRF//Z\n" \
+"END:VCARD"
Index: addressbook/libedata-book-dbus/e-book-backend-sync.c
===================================================================
--- addressbook/libedata-book-dbus/e-book-backend-sync.c	(revision 409)
+++ addressbook/libedata-book-dbus/e-book-backend-sync.c	(working copy)
@@ -1 +1,607 @@
-link ../libedata-book-orbit/e-book-backend-sync.c
\ No newline at end of file
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Author:
+ *   Chris Toshok (toshok@ximian.com)
+ *
+ * Copyright (C) 2003, Ximian, Inc.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-data-book-marshal.h"
+#include "e-book-backend-sync.h"
+
+struct _EBookBackendSyncPrivate {
+  int mumble;
+};
+
+static GObjectClass *parent_class;
+
+/**
+ * e_book_backend_sync_construct:
+ * @backend: an #EBookBackendSync
+ *
+ * Does nothing.
+ *
+ * Return value: %TRUE.
+ **/
+gboolean
+e_book_backend_sync_construct (EBookBackendSync *backend)
+{
+	return TRUE;
+}
+
+/**
+ * e_book_backend_sync_create_contact:
+ * @backend: an #EBookBackendSync
+ * @book: an #EDataBook
+ * @opid: the unique ID of the operation
+ * @vcard: a VCard representation of a contact
+ * @contact: a pointer to a location to store the resulting #EContact
+ *
+ * Creates a new contact with the contents of @vcard in @backend.
+ *
+ * Return value: An #EBookBackendSyncStatus indicating the outcome of the operation.
+ **/
+EBookBackendSyncStatus
+e_book_backend_sync_create_contact (EBookBackendSync *backend,
+				    EDataBook *book,
+				    guint32 opid,
+				    const char *vcard,
+				    EContact **contact)
+{
+	g_return_val_if_fail (E_IS_BOOK_BACKEND_SYNC (backend), GNOME_Evolution_Addressbook_OtherError);
+	g_return_val_if_fail (E_IS_DATA_BOOK (book), GNOME_Evolution_Addressbook_OtherError);
+	g_return_val_if_fail (vcard, GNOME_Evolution_Addressbook_OtherError);
+	g_return_val_if_fail (contact, GNOME_Evolution_Addressbook_OtherError);
+
+	g_assert (E_BOOK_BACKEND_SYNC_GET_CLASS (backend)->create_contact_sync);
+
+	return (* E_BOOK_BACKEND_SYNC_GET_CLASS (backend)->create_contact_sync) (backend, book, opid, vcard, contact);
+}
+
+/**
+ * e_book_backend_sync_remove:
+ * @backend: an #EBookBackendSync
+ * @book: an #EDataBook
+ * @opid: the unique ID of the operation
+ *
+ * Remove @book's database and storage overhead from the storage
+ * medium. This will delete all contacts in @book.
+ *
+ * Return value: An #EBookBackendSyncStatus indicating the outcome of the operation.
+ **/
+EBookBackendSyncStatus
+e_book_backend_sync_remove (EBookBackendSync *backend,
+			    EDataBook *book,
+			    guint32 opid)
+{
+	g_return_val_if_fail (E_IS_BOOK_BACKEND_SYNC (backend), GNOME_Evolution_Addressbook_OtherError);
+	g_return_val_if_fail (E_IS_DATA_BOOK (book), GNOME_Evolution_Addressbook_OtherError);
+
+	g_assert (E_BOOK_BACKEND_SYNC_GET_CLASS (backend)->remove_sync);
+
+	return (* E_BOOK_BACKEND_SYNC_GET_CLASS (backend)->remove_sync) (backend, book, opid);
+}
+
+/**
+ * e_book_backend_sync_remove_contacts:
+ * @backend: an #EBookBackendSync
+ * @book: an #EDataBook
+ * @opid: the unique ID of the operation
+ * @id_list: a #GList of pointers to unique contact ID strings
+ * @removed_ids: a pointer to a location to store a list of the contacts actually removed
+ *
+ * Removes the contacts specified by @id_list from @backend. The returned list
+ * of removed contacts is in the same format as the passed-in list, and must be
+ * freed by the caller.
+ *
+ * Return value: An #EBookBackendSyncStatus indicating the outcome of the operation.
+ **/
+EBookBackendSyncStatus
+e_book_backend_sync_remove_contacts (EBookBackendSync *backend,
+				     EDataBook *book,
+				     guint32 opid,
+				     GList *id_list,
+				     GList **removed_ids)
+{
+	g_return_val_if_fail (E_IS_BOOK_BACKEND_SYNC (backend), GNOME_Evolution_Addressbook_OtherError);
+	g_return_val_if_fail (E_IS_DATA_BOOK (book), GNOME_Evolution_Addressbook_OtherError);
+	g_return_val_if_fail (id_list, GNOME_Evolution_Addressbook_OtherError);
+	g_return_val_if_fail (removed_ids, GNOME_Evolution_Addressbook_OtherError);
+
+	g_assert (E_BOOK_BACKEND_SYNC_GET_CLASS (backend)->remove_contacts_sync);
+
+	return (* E_BOOK_BACKEND_SYNC_GET_CLASS (backend)->remove_contacts_sync) (backend, book, opid, id_list, removed_ids);
+}
+
+/**
+ * e_book_backend_sync_modify_contact:
+ * @backend: an #EBookBackendSync
+ * @book: an #EDataBook
+ * @opid: the unique ID of the operation
+ * @vcard: the string representation of a contact
+ * @contact: a pointer to a location to store the resulting #EContact
+ * 
+ * Modifies the contact specified by the ID embedded in @vcard, to
+ * reflect the full contents of @vcard.
+ *
+ * Return value: An #EBookBackendSyncStatus indicating the outcome of the operation.
+ **/
+EBookBackendSyncStatus
+e_book_backend_sync_modify_contact (EBookBackendSync *backend,
+				    EDataBook *book,
+				    guint32 opid,
+				    const char *vcard,
+				    EContact **contact)
+{
+	g_return_val_if_fail (E_IS_BOOK_BACKEND_SYNC (backend), GNOME_Evolution_Addressbook_OtherError);
+	g_return_val_if_fail (E_IS_DATA_BOOK (book), GNOME_Evolution_Addressbook_OtherError);
+	g_return_val_if_fail (vcard, GNOME_Evolution_Addressbook_OtherError);
+	g_return_val_if_fail (contact, GNOME_Evolution_Addressbook_OtherError);
+
+	g_assert (E_BOOK_BACKEND_SYNC_GET_CLASS (backend)->modify_contact_sync);
+
+	return (* E_BOOK_BACKEND_SYNC_GET_CLASS (backend)->modify_contact_sync) (backend, book, opid, vcard, contact);
+}
+
+/**
+ * e_book_backend_sync_get_contact:
+ * @backend: an #EBookBackendSync
+ * @book: an #EDataBook
+ * @opid: the unique ID of the operation
+ * @id: a unique contact ID
+ * @vcard: a pointer to a location to store the resulting VCard string
+ *
+ * Gets a contact from @book.
+ *
+ * Return value: An #EBookBackendSyncStatus indicating the outcome of the operation.
+ **/
+EBookBackendSyncStatus
+e_book_backend_sync_get_contact (EBookBackendSync *backend,
+				 EDataBook *book,
+				 guint32 opid,
+				 const char *id,
+				 char **vcard)
+{
+	g_return_val_if_fail (E_IS_BOOK_BACKEND_SYNC (backend), GNOME_Evolution_Addressbook_OtherError);
+	g_return_val_if_fail (E_IS_DATA_BOOK (book), GNOME_Evolution_Addressbook_OtherError);
+	g_return_val_if_fail (id, GNOME_Evolution_Addressbook_OtherError);
+	g_return_val_if_fail (vcard, GNOME_Evolution_Addressbook_OtherError);
+
+	g_assert (E_BOOK_BACKEND_SYNC_GET_CLASS (backend)->get_contact_sync);
+
+	return (* E_BOOK_BACKEND_SYNC_GET_CLASS (backend)->get_contact_sync) (backend, book, opid, id, vcard);
+}
+
+/**
+ * e_book_backend_sync_get_contact_list:
+ * @backend: an #EBookBackendSync
+ * @book: an #EDataBook
+ * @opid: the unique ID of the operation
+ * @query: an s-expression of the query to perform
+ * @contacts: a pointer to a location to store the resulting list of VCard strings
+ *
+ * Gets a list of contacts from @book. The list and its elements must be freed
+ * by the caller.
+ * 
+ * Return value: An #EBookBackendSyncStatus indicating the outcome of the operation.
+ **/
+EBookBackendSyncStatus
+e_book_backend_sync_get_contact_list (EBookBackendSync *backend,
+				      EDataBook *book,
+				      guint32 opid,
+				      const char *query,
+				      GList **contacts)
+{
+	g_return_val_if_fail (E_IS_BOOK_BACKEND_SYNC (backend), GNOME_Evolution_Addressbook_OtherError);
+	g_return_val_if_fail (E_IS_DATA_BOOK (book), GNOME_Evolution_Addressbook_OtherError);
+	g_return_val_if_fail (query, GNOME_Evolution_Addressbook_OtherError);
+	g_return_val_if_fail (contacts, GNOME_Evolution_Addressbook_OtherError);
+
+	g_assert (E_BOOK_BACKEND_SYNC_GET_CLASS (backend)->get_contact_list_sync);
+
+	return (* E_BOOK_BACKEND_SYNC_GET_CLASS (backend)->get_contact_list_sync) (backend, book, opid, query, contacts);
+}
+
+/**
+ * e_book_backend_sync_get_changes:
+ * @backend: an #EBookBackendSync
+ * @book: an #EDataBook
+ * @opid: the unique ID of the operation
+ * @change_id: a unique changes ID
+ * @changes: a pointer to a location to store the resulting list of changes
+ *
+ * Gets the changes made to @book since the last call to this function.
+ * The returned list will contain items of CORBA type
+ * #GNOME_Evolution_Addressbook_BookChangeItem.
+ *
+ * Return value: An #EBookBackendSyncStatus indicating the outcome of the operation.
+ **/
+EBookBackendSyncStatus
+e_book_backend_sync_get_changes (EBookBackendSync *backend,
+				 EDataBook *book,
+				 guint32 opid,
+				 const char *change_id,
+				 GList **changes)
+{
+	g_return_val_if_fail (E_IS_BOOK_BACKEND_SYNC (backend), GNOME_Evolution_Addressbook_OtherError);
+	g_return_val_if_fail (E_IS_DATA_BOOK (book), GNOME_Evolution_Addressbook_OtherError);
+	g_return_val_if_fail (change_id, GNOME_Evolution_Addressbook_OtherError);
+	g_return_val_if_fail (changes, GNOME_Evolution_Addressbook_OtherError);
+
+	g_assert (E_BOOK_BACKEND_SYNC_GET_CLASS (backend)->get_changes_sync);
+
+	return (* E_BOOK_BACKEND_SYNC_GET_CLASS (backend)->get_changes_sync) (backend, book, opid, change_id, changes);
+}
+
+/**
+ * e_book_backend_sync_authenticate_user:
+ * @backend: an #EBookBackendSync
+ * @book: an #EDataBook
+ * @opid: the unique ID of the operation
+ * @user: the user's name
+ * @passwd: the user's password
+ * @auth_method: the authentication method desired
+ *
+ * Authenticates @user against @book.
+ *
+ * Return value: An #EBookBackendSyncStatus indicating the outcome of the operation.
+ **/
+EBookBackendSyncStatus
+e_book_backend_sync_authenticate_user (EBookBackendSync *backend,
+				       EDataBook *book,
+				       guint32 opid,
+				       const char *user,
+				       const char *passwd,
+				       const char *auth_method)
+{
+	g_return_val_if_fail (E_IS_BOOK_BACKEND_SYNC (backend), GNOME_Evolution_Addressbook_OtherError);
+	g_return_val_if_fail (E_IS_DATA_BOOK (book), GNOME_Evolution_Addressbook_OtherError);
+	g_return_val_if_fail (user && passwd && auth_method, GNOME_Evolution_Addressbook_OtherError);
+
+	g_assert (E_BOOK_BACKEND_SYNC_GET_CLASS (backend)->authenticate_user_sync);
+
+	return (* E_BOOK_BACKEND_SYNC_GET_CLASS (backend)->authenticate_user_sync) (backend, book, opid, user, passwd, auth_method);
+}
+
+/**
+ * e_book_backend_sync_get_required_fields:
+ * @backend: an #EBookBackendSync
+ * @book: an #EDataBook
+ * @opid: the unique ID of the operation
+ * @fields: a pointer to a location to store the fields
+ *
+ * Gets a list of the fields required for all contacts in @book. The
+ * fields are represented by strings from #e_contact_field_name. The list
+ * and its contents must be freed by the caller.
+ *
+ * Return value: An #EBookBackendSyncStatus indicating the outcome of the operation.
+ **/
+EBookBackendSyncStatus
+e_book_backend_sync_get_required_fields (EBookBackendSync *backend,
+					  EDataBook *book,
+					  guint32 opid,
+					  GList **fields)
+{
+	g_return_val_if_fail (E_IS_BOOK_BACKEND_SYNC (backend), GNOME_Evolution_Addressbook_OtherError);
+	g_return_val_if_fail (E_IS_DATA_BOOK (book), GNOME_Evolution_Addressbook_OtherError);
+	g_return_val_if_fail (fields, GNOME_Evolution_Addressbook_OtherError);
+	
+	g_assert (E_BOOK_BACKEND_SYNC_GET_CLASS (backend)->get_required_fields_sync);
+
+	return (* E_BOOK_BACKEND_SYNC_GET_CLASS (backend)->get_required_fields_sync) (backend, book, opid, fields);
+}
+
+/**
+ * e_book_backend_sync_get_supported_fields:
+ * @backend: an #EBookBackendSync
+ * @book: an #EDataBook
+ * @opid: the unique ID of the operation
+ * @fields: a pointer to a location to store the fields
+ *
+ * Gets a list of the fields supported for contacts in @book. Other fields
+ * may not be stored. The fields are represented by strings from #e_contact_field_name.
+ * The list and its contents must be freed by the caller.
+ *
+ * Return value: An #EBookBackendSyncStatus indicating the outcome of the operation.
+ **/
+EBookBackendSyncStatus
+e_book_backend_sync_get_supported_fields (EBookBackendSync *backend,
+					  EDataBook *book,
+					  guint32 opid,
+					  GList **fields)
+{
+	g_return_val_if_fail (E_IS_BOOK_BACKEND_SYNC (backend), GNOME_Evolution_Addressbook_OtherError);
+	g_return_val_if_fail (E_IS_DATA_BOOK (book), GNOME_Evolution_Addressbook_OtherError);
+	g_return_val_if_fail (fields, GNOME_Evolution_Addressbook_OtherError);
+	
+	g_assert (E_BOOK_BACKEND_SYNC_GET_CLASS (backend)->get_supported_fields_sync);
+
+	return (* E_BOOK_BACKEND_SYNC_GET_CLASS (backend)->get_supported_fields_sync) (backend, book, opid, fields);
+}
+
+/**
+ * e_book_backend_sync_get_supported_auth_methods:
+ * @backend: an #EBookBackendSync
+ * @book: an #EDataBook
+ * @opid: the unique ID of the operation
+ * @methods: a pointer to a location to store the methods
+ *
+ * Gets a list of the authentication methods supported by @book. The
+ * methods are represented by strings. The list and its contents must
+ * be freed by the caller.
+ *
+ * Return value: An #EBookBackendSyncStatus indicating the outcome of the operation.
+ **/
+EBookBackendSyncStatus
+e_book_backend_sync_get_supported_auth_methods (EBookBackendSync *backend,
+						EDataBook *book,
+						guint32 opid,
+						GList **methods)
+{
+	g_return_val_if_fail (E_IS_BOOK_BACKEND_SYNC (backend), GNOME_Evolution_Addressbook_OtherError);
+	g_return_val_if_fail (E_IS_DATA_BOOK (book), GNOME_Evolution_Addressbook_OtherError);
+	g_return_val_if_fail (methods, GNOME_Evolution_Addressbook_OtherError);
+
+	g_assert (E_BOOK_BACKEND_SYNC_GET_CLASS (backend)->get_supported_auth_methods_sync);
+
+	return (* E_BOOK_BACKEND_SYNC_GET_CLASS (backend)->get_supported_auth_methods_sync) (backend, book, opid, methods);
+}
+
+static void
+_e_book_backend_remove (EBookBackend *backend,
+			EDataBook    *book,
+			guint32       opid)
+{
+	EBookBackendSyncStatus status;
+
+	status = e_book_backend_sync_remove (E_BOOK_BACKEND_SYNC (backend), book, opid);
+
+	e_data_book_respond_remove (book, opid, status);
+}
+
+static void
+_e_book_backend_create_contact (EBookBackend *backend,
+				EDataBook    *book,
+				guint32       opid,
+				const char   *vcard)
+{
+	EBookBackendSyncStatus status;
+	EContact *contact = NULL;
+
+	status = e_book_backend_sync_create_contact (E_BOOK_BACKEND_SYNC (backend), book, opid, vcard, &contact);
+
+	e_data_book_respond_create (book, opid, status, contact);
+
+	if (contact)
+		g_object_unref (contact);
+}
+
+static void
+_e_book_backend_remove_contacts (EBookBackend *backend,
+				 EDataBook    *book,
+				 guint32       opid,
+				 GList        *id_list)
+{
+	EBookBackendSyncStatus status;
+	GList *ids = NULL;
+
+	status = e_book_backend_sync_remove_contacts (E_BOOK_BACKEND_SYNC (backend), book, opid, id_list, &ids);
+
+	e_data_book_respond_remove_contacts (book, opid, status, ids);
+
+	if (ids)
+		g_list_free (ids);
+}
+
+static void
+_e_book_backend_modify_contact (EBookBackend *backend,
+				EDataBook    *book,
+				guint32       opid,
+				const char   *vcard)
+{
+	EBookBackendSyncStatus status;
+	EContact *contact = NULL;
+
+	status = e_book_backend_sync_modify_contact (E_BOOK_BACKEND_SYNC (backend), book, opid, vcard, &contact);
+
+	e_data_book_respond_modify (book, opid, status, contact);
+
+	if (contact)
+		g_object_unref (contact);
+}
+
+static void
+_e_book_backend_get_contact (EBookBackend *backend,
+			     EDataBook    *book,
+			     guint32       opid,
+			     const char   *id)
+{
+	EBookBackendSyncStatus status;
+	char *vcard = NULL;
+
+	status = e_book_backend_sync_get_contact (E_BOOK_BACKEND_SYNC (backend), book, opid, id, &vcard);
+
+	e_data_book_respond_get_contact (book, opid, status, vcard);
+
+	if (vcard)
+		g_free (vcard);
+}
+
+static void
+_e_book_backend_get_contact_list (EBookBackend *backend,
+				  EDataBook    *book,
+				  guint32       opid,
+				  const char   *query)
+{
+	EBookBackendSyncStatus status;
+	GList *cards = NULL;
+
+	status = e_book_backend_sync_get_contact_list (E_BOOK_BACKEND_SYNC (backend), book, opid, query, &cards);
+
+	e_data_book_respond_get_contact_list (book, opid, status, cards);
+}
+
+static void
+_e_book_backend_get_changes (EBookBackend *backend,
+			     EDataBook    *book,
+			     guint32       opid,
+			     const char   *change_id)
+{
+	EBookBackendSyncStatus status;
+	GList *changes = NULL;
+
+	status = e_book_backend_sync_get_changes (E_BOOK_BACKEND_SYNC (backend), book, opid, change_id, &changes);
+
+	e_data_book_respond_get_changes (book, opid, status, changes);
+}
+
+static void
+_e_book_backend_authenticate_user (EBookBackend *backend,
+				   EDataBook    *book,
+				   guint32       opid,
+				   const char   *user,
+				   const char   *passwd,
+				   const char   *auth_method)
+{
+	EBookBackendSyncStatus status;
+
+	status = e_book_backend_sync_authenticate_user (E_BOOK_BACKEND_SYNC (backend), book, opid, user, passwd, auth_method);
+
+	e_data_book_respond_authenticate_user (book, opid, status);
+}
+
+static void
+_e_book_backend_get_required_fields (EBookBackend *backend,
+				      EDataBook    *book,
+				      guint32       opid)
+{
+	EBookBackendSyncStatus status;
+	GList *fields = NULL;
+
+	status = e_book_backend_sync_get_required_fields (E_BOOK_BACKEND_SYNC (backend), book, opid, &fields);
+
+	e_data_book_respond_get_required_fields (book, opid, status, fields);
+
+	if (fields) {
+		g_list_foreach (fields, (GFunc)g_free, NULL);
+		g_list_free (fields);
+	}
+}
+
+static void
+_e_book_backend_get_supported_fields (EBookBackend *backend,
+				      EDataBook    *book,
+				      guint32       opid)
+{
+	EBookBackendSyncStatus status;
+	GList *fields = NULL;
+
+	status = e_book_backend_sync_get_supported_fields (E_BOOK_BACKEND_SYNC (backend), book, opid, &fields);
+
+	e_data_book_respond_get_supported_fields (book, opid, status, fields);
+
+	if (fields) {
+		g_list_foreach (fields, (GFunc)g_free, NULL);
+		g_list_free (fields);
+	}
+}
+
+static void
+_e_book_backend_get_supported_auth_methods (EBookBackend *backend,
+					    EDataBook    *book,
+					    guint32       opid)
+{
+	EBookBackendSyncStatus status;
+	GList *methods = NULL;
+
+	status = e_book_backend_sync_get_supported_auth_methods (E_BOOK_BACKEND_SYNC (backend), book, opid, &methods);
+
+	e_data_book_respond_get_supported_auth_methods (book, opid, status, methods);
+
+	if (methods) {
+		g_list_foreach (methods, (GFunc)g_free, NULL);
+		g_list_free (methods);
+	}
+}
+
+static void
+e_book_backend_sync_init (EBookBackendSync *backend)
+{
+	EBookBackendSyncPrivate *priv;
+
+	priv          = g_new0 (EBookBackendSyncPrivate, 1);
+
+	backend->priv = priv;
+}
+
+static void
+e_book_backend_sync_dispose (GObject *object)
+{
+	EBookBackendSync *backend;
+
+	backend = E_BOOK_BACKEND_SYNC (object);
+
+	if (backend->priv) {
+		g_free (backend->priv);
+
+		backend->priv = NULL;
+	}
+
+	G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+e_book_backend_sync_class_init (EBookBackendSyncClass *klass)
+{
+	GObjectClass *object_class;
+	EBookBackendClass *backend_class = E_BOOK_BACKEND_CLASS (klass);
+
+	parent_class = g_type_class_peek_parent (klass);
+
+	object_class = (GObjectClass *) klass;
+
+	backend_class->remove = _e_book_backend_remove;
+	backend_class->create_contact = _e_book_backend_create_contact;
+	backend_class->remove_contacts = _e_book_backend_remove_contacts;
+	backend_class->modify_contact = _e_book_backend_modify_contact;
+	backend_class->get_contact = _e_book_backend_get_contact;
+	backend_class->get_contact_list = _e_book_backend_get_contact_list;
+	backend_class->get_changes = _e_book_backend_get_changes;
+	backend_class->authenticate_user = _e_book_backend_authenticate_user;
+	backend_class->get_required_fields = _e_book_backend_get_required_fields;
+	backend_class->get_supported_fields = _e_book_backend_get_supported_fields;
+	backend_class->get_supported_auth_methods = _e_book_backend_get_supported_auth_methods;
+
+	object_class->dispose = e_book_backend_sync_dispose;
+}
+
+/**
+ * e_book_backend_get_type:
+ */
+GType
+e_book_backend_sync_get_type (void)
+{
+	static GType type = 0;
+
+	if (! type) {
+		GTypeInfo info = {
+			sizeof (EBookBackendSyncClass),
+			NULL, /* base_class_init */
+			NULL, /* base_class_finalize */
+			(GClassInitFunc)  e_book_backend_sync_class_init,
+			NULL, /* class_finalize */
+			NULL, /* class_data */
+			sizeof (EBookBackendSync),
+			0,    /* n_preallocs */
+			(GInstanceInitFunc) e_book_backend_sync_init
+		};
+
+		type = g_type_register_static (E_TYPE_BOOK_BACKEND, "EBookBackendSync", &info, 0);
+	}
+
+	return type;
+}
Index: addressbook/libedata-book-dbus/e-book-backend-sexp.c
===================================================================
--- addressbook/libedata-book-dbus/e-book-backend-sexp.c	(revision 409)
+++ addressbook/libedata-book-dbus/e-book-backend-sexp.c	(working copy)
@@ -1 +1,655 @@
-link ../libedata-book-orbit/e-book-backend-sexp.c
\ No newline at end of file
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* 
+ * pas-backend-card-sexp.c
+ * Copyright 1999, 2000, 2001, Ximian, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License, version 2, as published by the Free Software Foundation.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include <string.h>
+#include <libedataserver/e-sexp.h>
+#include <libedataserver/e-util.h>
+#include "e-book-backend-sexp.h"
+
+static GObjectClass *parent_class;
+
+typedef struct _SearchContext SearchContext;
+
+struct _EBookBackendSExpPrivate {
+	ESExp *search_sexp;
+	SearchContext *search_context;
+};
+
+struct _SearchContext {
+	EContact *contact;
+};
+
+static gboolean
+compare_im (EContact *contact, const char *str,
+	    char *(*compare)(const char*, const char*),
+	    EContactField im_field)
+{
+	GList    *aims, *l;
+	gboolean  found_it = FALSE;
+
+	aims = e_contact_get (contact, im_field);
+
+	for (l = aims; l != NULL; l = l->next) {
+		char *im = (char *) l->data;
+
+		if (im && compare (im, str)) {
+			found_it = TRUE;
+			break;
+		}
+	}
+	
+	g_list_foreach (aims, (GFunc)g_free, NULL);
+	g_list_free (aims);
+
+	return found_it;
+}
+
+static gboolean
+compare_im_aim (EContact *contact, const char *str,
+		char *(*compare)(const char*, const char*))
+{
+	return compare_im (contact, str, compare, E_CONTACT_IM_AIM);
+}
+
+static gboolean
+compare_im_msn (EContact *contact, const char *str,
+		char *(*compare)(const char*, const char*))
+{
+	return compare_im (contact, str, compare, E_CONTACT_IM_MSN);
+}
+
+static gboolean
+compare_im_icq (EContact *contact, const char *str,
+		char *(*compare)(const char*, const char*))
+{
+	return compare_im (contact, str, compare, E_CONTACT_IM_ICQ);
+}
+
+static gboolean
+compare_im_yahoo (EContact *contact, const char *str,
+		  char *(*compare)(const char*, const char*))
+{
+	return compare_im (contact, str, compare, E_CONTACT_IM_YAHOO);
+}
+
+static gboolean
+compare_im_jabber (EContact *contact, const char *str,
+		   char *(*compare)(const char*, const char*))
+{
+	return compare_im (contact, str, compare, E_CONTACT_IM_JABBER);
+}
+
+static gboolean
+compare_im_groupwise (EContact *contact, const char *str,
+		      char *(*compare)(const char*, const char*))
+{
+	return compare_im (contact, str, compare, E_CONTACT_IM_GROUPWISE);
+}
+
+static gboolean
+compare_email (EContact *contact, const char *str,
+	       char *(*compare)(const char*, const char*))
+{
+	int i;
+
+	for (i = E_CONTACT_EMAIL_1; i <= E_CONTACT_EMAIL_4; i ++) {
+		const char *email = e_contact_get_const (contact, i);
+
+		if (email && compare(email, str))
+			return TRUE;
+	}
+
+	return FALSE;
+}
+
+static gboolean
+compare_phone (EContact *contact, const char *str,
+	       char *(*compare)(const char*, const char*))
+{
+	int i;
+	gboolean rv = FALSE;
+
+	for (i = E_CONTACT_FIRST_PHONE_ID; i <= E_CONTACT_LAST_PHONE_ID; i ++) {
+		char *phone = e_contact_get (contact, i);
+
+		rv = phone && compare(phone, str);
+		g_free (phone);
+
+		if (rv)
+			break;
+	}
+
+	return rv;
+}
+
+static gboolean
+compare_name (EContact *contact, const char *str,
+	      char *(*compare)(const char*, const char*))
+{
+	const char *name;
+
+	name = e_contact_get_const (contact, E_CONTACT_FULL_NAME);
+	if (name && compare (name, str))
+		return TRUE;
+
+	name = e_contact_get_const (contact, E_CONTACT_FAMILY_NAME);
+	if (name && compare (name, str))
+		return TRUE;
+	
+	name = e_contact_get_const (contact, E_CONTACT_GIVEN_NAME);
+	if (name && compare (name, str))
+		return TRUE;
+
+	name = e_contact_get_const (contact, E_CONTACT_NICKNAME);
+	if (name && compare (name, str))
+		return TRUE;
+
+	return FALSE;
+}
+
+static gboolean
+compare_address (EContact *contact, const char *str,
+		 char *(*compare)(const char*, const char*))
+{
+	
+	int i;
+	gboolean rv = FALSE;
+
+	for (i = E_CONTACT_FIRST_ADDRESS_ID; i <= E_CONTACT_LAST_ADDRESS_ID; i ++) {
+		EContactAddress *address = e_contact_get (contact, i);
+		if (address) {
+			rv =  (address->po && compare(address->po, str)) ||
+				(address->street && compare(address->street, str)) ||
+				(address->ext && compare(address->ext, str)) ||
+				(address->locality && compare(address->locality, str)) ||
+				(address->region && compare(address->region, str)) || 
+				(address->code && compare(address->code, str)) ||
+				(address->country && compare(address->country, str));
+			
+			e_contact_address_free (address);
+		
+			if (rv)
+				break;
+		}
+	}
+
+	return rv;
+	
+}
+
+static gboolean
+compare_category (EContact *contact, const char *str,
+		  char *(*compare)(const char*, const char*))
+{
+	GList *categories;
+	GList *iterator;
+	gboolean ret_val = FALSE;
+
+	categories = e_contact_get (contact, E_CONTACT_CATEGORY_LIST);
+
+	for (iterator = categories; iterator; iterator = iterator->next) {
+		const char *category = iterator->data;
+
+		if (compare(category, str)) {
+			ret_val = TRUE;
+			break;
+		}
+	}
+
+	g_list_foreach (categories, (GFunc)g_free, NULL);
+	g_list_free (categories);
+
+	return ret_val;
+}
+
+enum prop_type {
+	PROP_TYPE_NORMAL,
+	PROP_TYPE_LIST
+};
+static struct prop_info {
+	EContactField field_id;
+	const char *query_prop;
+	enum prop_type prop_type;
+	gboolean (*list_compare)(EContact *contact, const char *str,
+				 char *(*compare)(const char*, const char*));
+
+} prop_info_table[] = {
+#define NORMAL_PROP(f,q) {f, q, PROP_TYPE_NORMAL, NULL}
+#define LIST_PROP(q,c) {0, q, PROP_TYPE_LIST, c}
+
+	/* query prop,   type,              list compare function */
+	NORMAL_PROP ( E_CONTACT_FILE_AS, "file-as" ),
+	NORMAL_PROP ( E_CONTACT_UID, "id" ),
+	LIST_PROP ( "full-name", compare_name), /* not really a list, but we need to compare both full and surname */
+	NORMAL_PROP ( E_CONTACT_HOMEPAGE_URL, "url"),
+	NORMAL_PROP ( E_CONTACT_BLOG_URL, "blog-url"),
+	NORMAL_PROP ( E_CONTACT_CALENDAR_URI, "calurl"),
+	NORMAL_PROP ( E_CONTACT_FREEBUSY_URL, "fburl"),
+	NORMAL_PROP ( E_CONTACT_ICS_CALENDAR, "icscalendar"),
+	NORMAL_PROP ( E_CONTACT_VIDEO_URL, "video-url"),
+
+	NORMAL_PROP ( E_CONTACT_MAILER, "mailer"),
+	NORMAL_PROP ( E_CONTACT_ORG, "org"),
+	NORMAL_PROP ( E_CONTACT_ORG_UNIT, "org-unit"),
+	NORMAL_PROP ( E_CONTACT_OFFICE, "office"),
+	NORMAL_PROP ( E_CONTACT_TITLE, "title"),
+	NORMAL_PROP ( E_CONTACT_ROLE, "role"),
+	NORMAL_PROP ( E_CONTACT_MANAGER, "manager"),
+	NORMAL_PROP ( E_CONTACT_ASSISTANT, "assistant"),
+	NORMAL_PROP ( E_CONTACT_NICKNAME, "nickname"),
+	NORMAL_PROP ( E_CONTACT_SPOUSE, "spouse" ),
+	NORMAL_PROP ( E_CONTACT_NOTE, "note"),
+	LIST_PROP ( "im-aim",    compare_im_aim ),
+	LIST_PROP ( "im-msn",    compare_im_msn ),
+	LIST_PROP ( "im-icq",    compare_im_icq ),
+	LIST_PROP ( "im-jabber", compare_im_jabber ),
+	LIST_PROP ( "im-yahoo",  compare_im_yahoo ),
+	LIST_PROP ( "im-groupwise", compare_im_groupwise ),
+	LIST_PROP ( "email",     compare_email ),
+	LIST_PROP ( "phone",     compare_phone ),
+	LIST_PROP ( "address",   compare_address ),
+	LIST_PROP ( "category-list",  compare_category ),
+};
+static int num_prop_infos = sizeof(prop_info_table) / sizeof(prop_info_table[0]);
+
+static ESExpResult *
+entry_compare(SearchContext *ctx, struct _ESExp *f,
+	      int argc, struct _ESExpResult **argv,
+	      char *(*compare)(const char*, const char*))
+{
+	ESExpResult *r;
+	int truth = FALSE;
+
+	if (argc == 2
+	    && argv[0]->type == ESEXP_RES_STRING
+	    && argv[1]->type == ESEXP_RES_STRING) {
+		char *propname;
+		struct prop_info *info = NULL;
+		int i;
+		gboolean any_field;
+
+		propname = argv[0]->value.string;
+
+		any_field = !strcmp(propname, "x-evolution-any-field");
+		for (i = 0; i < num_prop_infos; i ++) {
+			if (any_field
+			    || !strcmp (prop_info_table[i].query_prop, propname)) {
+				info = &prop_info_table[i];
+		
+				if (any_field && info->field_id == E_CONTACT_UID) {
+					/* We need to skip UID from any field contains search
+					 * any-field search should be supported for the 
+					 * visible fields only.
+					 */
+					truth = FALSE;
+				}
+				else if (info->prop_type == PROP_TYPE_NORMAL) {
+					const char *prop = NULL;
+					/* straight string property matches */
+					
+					prop = e_contact_get_const (ctx->contact, info->field_id);
+
+					if (prop && compare(prop, argv[1]->value.string)) {
+						truth = TRUE;
+					}
+					if ((!prop) && compare("", argv[1]->value.string)) {
+						truth = TRUE;
+					}
+				}
+				else if (info->prop_type == PROP_TYPE_LIST) {
+					/* the special searches that match any of the list elements */
+					truth = info->list_compare (ctx->contact, argv[1]->value.string, compare);
+				}
+
+				/* if we're looking at all fields and find a match,
+				   or if we're just looking at this one field,
+				   break. */
+				if ((any_field && truth)
+				    || !any_field)
+					break;
+			}
+		}
+		
+	}
+	r = e_sexp_result_new(f, ESEXP_RES_BOOL);
+	r->value.bool = truth;
+
+	return r;
+}
+
+static ESExpResult *
+func_contains(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
+{
+	SearchContext *ctx = data;
+
+	return entry_compare (ctx, f, argc, argv, (char *(*)(const char*, const char*)) e_util_utf8_strstrcase);
+}
+
+static char *
+is_helper (const char *s1, const char *s2)
+{
+	if (!strcasecmp(s1, s2))
+		return (char*)s1;
+	else
+		return NULL;
+}
+
+static ESExpResult *
+func_is(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
+{
+	SearchContext *ctx = data;
+
+	return entry_compare (ctx, f, argc, argv, is_helper);
+}
+
+static char *
+endswith_helper (const char *s1, const char *s2)
+{
+	char *p;
+	if ((p = (char*) e_util_utf8_strstrcase(s1, s2))
+	    && (strlen(p) == strlen(s2)))
+		return p;
+	else
+		return NULL;
+}
+
+static ESExpResult *
+func_endswith(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
+{
+	SearchContext *ctx = data;
+
+	return entry_compare (ctx, f, argc, argv, endswith_helper);
+}
+
+static char *
+beginswith_helper (const char *s1, const char *s2)
+{
+	char *p;
+	if ((p = (char*) e_util_utf8_strstrcase(s1, s2))
+	    && (p == s1))
+		return p;
+	else
+		return NULL;
+}
+
+static ESExpResult *
+func_beginswith(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
+{
+	SearchContext *ctx = data;
+
+	return entry_compare (ctx, f, argc, argv, beginswith_helper);
+}
+
+static ESExpResult *
+func_exists(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
+{
+	SearchContext *ctx = data;
+	ESExpResult *r;
+	int truth = FALSE;
+
+	if (argc == 1
+	    && argv[0]->type == ESEXP_RES_STRING) {
+		char *propname;
+		struct prop_info *info = NULL;
+		int i;
+
+		propname = argv[0]->value.string;
+
+		for (i = 0; i < num_prop_infos; i ++) {
+			if (!strcmp (prop_info_table[i].query_prop, propname)) {
+				info = &prop_info_table[i];
+				
+				if (info->prop_type == PROP_TYPE_NORMAL) {
+					const char *prop = NULL;
+					/* searches where the query's property
+					   maps directly to an ecard property */
+					
+					prop = e_contact_get_const (ctx->contact, info->field_id);
+
+					if (prop && *prop)
+						truth = TRUE;
+				}
+				else if (info->prop_type == PROP_TYPE_LIST) {
+				/* the special searches that match any of the list elements */
+					truth = info->list_compare (ctx->contact, "", (char *(*)(const char*, const char*)) e_util_utf8_strstrcase);
+				}
+
+				break;
+			}
+		}
+		
+	}
+	r = e_sexp_result_new(f, ESEXP_RES_BOOL);
+	r->value.bool = truth;
+
+	return r;
+}
+
+static ESExpResult *
+func_exists_vcard(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
+{
+	SearchContext *ctx = data;
+	ESExpResult *r;
+	int truth = FALSE;
+
+	if (argc == 1 && argv[0]->type == ESEXP_RES_STRING) {
+		const char *attr_name;
+		EVCardAttribute *attr;
+		GList *values;
+		char *s;
+
+		attr_name = argv[0]->value.string;
+		attr = e_vcard_get_attribute (E_VCARD (ctx->contact), attr_name);
+		if (attr) {
+			values = e_vcard_attribute_get_values (attr);
+			if (g_list_length (values) > 0) {
+				s = values->data;
+				if (s[0] != '\0') {
+					truth = TRUE;
+				}
+			}
+		}
+	}
+	
+	r = e_sexp_result_new(f, ESEXP_RES_BOOL);
+	r->value.bool = truth;
+
+	return r;
+}
+
+/* 'builtin' functions */
+static struct {
+	char *name;
+	ESExpFunc *func;
+	int type;		/* set to 1 if a function can perform shortcut evaluation, or
+				   doesn't execute everything, 0 otherwise */
+} symbols[] = {
+	{ "contains", func_contains, 0 },
+	{ "is", func_is, 0 },
+	{ "beginswith", func_beginswith, 0 },
+	{ "endswith", func_endswith, 0 },
+	{ "exists", func_exists, 0 },
+	{ "exists_vcard", func_exists_vcard, 0 },
+};
+
+/**
+ * e_book_backend_sexp_match_contact:
+ * @sexp: an #EBookBackendSExp
+ * @contact: an #EContact
+ *
+ * Checks if @contact matches @sexp.
+ *
+ * Return value: %TRUE if the contact matches, %FALSE otherwise.
+ **/
+gboolean
+e_book_backend_sexp_match_contact (EBookBackendSExp *sexp, EContact *contact)
+{
+	ESExpResult *r;
+	gboolean retval;
+
+	if (!contact) {
+		g_warning ("null EContact passed to e_book_backend_sexp_match_contact");
+		return FALSE;
+	}
+
+	sexp->priv->search_context->contact = g_object_ref (contact);
+
+	r = e_sexp_eval(sexp->priv->search_sexp);
+
+	retval = (r && r->type == ESEXP_RES_BOOL && r->value.bool);
+
+	g_object_unref(sexp->priv->search_context->contact);
+
+	e_sexp_result_free(sexp->priv->search_sexp, r);
+
+	return retval;
+}
+
+/**
+ * e_book_backend_sexp_match_vcard:
+ * @sexp: an #EBookBackendSExp
+ * @vcard: a VCard string
+ *
+ * Checks if @vcard matches @sexp.
+ *
+ * Return value: %TRUE if the VCard matches, %FALSE otherwise.
+ **/
+gboolean
+e_book_backend_sexp_match_vcard (EBookBackendSExp *sexp, const char *vcard)
+{
+	EContact *contact;
+	gboolean retval;
+
+	contact = e_contact_new_from_vcard (vcard);
+
+	retval = e_book_backend_sexp_match_contact (sexp, contact);
+
+	g_object_unref(contact);
+
+	return retval;
+}
+
+
+
+/**
+ * e_book_backend_sexp_new:
+ * @text: an s-expression to parse
+ *
+ * Creates a new #EBookBackendSExp from @text.
+ *
+ * Return value: A new #EBookBackendSExp.
+ **/
+EBookBackendSExp *
+e_book_backend_sexp_new (const char *text)
+{
+	EBookBackendSExp *sexp = g_object_new (E_TYPE_BACKEND_SEXP, NULL);
+	int esexp_error;
+	int i;
+
+	sexp->priv->search_sexp = e_sexp_new();
+
+	for(i=0;i<sizeof(symbols)/sizeof(symbols[0]);i++) {
+		if (symbols[i].type == 1) {
+			e_sexp_add_ifunction(sexp->priv->search_sexp, 0, symbols[i].name,
+					     (ESExpIFunc *)symbols[i].func, sexp->priv->search_context);
+		}
+		else {
+			e_sexp_add_function(sexp->priv->search_sexp, 0, symbols[i].name,
+					    symbols[i].func, sexp->priv->search_context);
+		}
+	}
+
+	e_sexp_input_text(sexp->priv->search_sexp, text, strlen(text));
+	esexp_error = e_sexp_parse(sexp->priv->search_sexp);
+
+	if (esexp_error == -1) {
+		g_object_unref (sexp);
+		sexp = NULL;
+	}
+
+	return sexp;
+}
+
+static void
+e_book_backend_sexp_dispose (GObject *object)
+{
+	EBookBackendSExp *sexp = E_BOOK_BACKEND_SEXP (object);
+
+	if (sexp->priv) {
+		e_sexp_unref(sexp->priv->search_sexp);
+
+		g_free (sexp->priv->search_context);
+		g_free (sexp->priv);
+		sexp->priv = NULL;
+	}
+
+	if (G_OBJECT_CLASS (parent_class)->dispose)
+		G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+e_book_backend_sexp_class_init (EBookBackendSExpClass *klass)
+{
+	GObjectClass  *object_class = G_OBJECT_CLASS (klass);
+
+	parent_class = g_type_class_peek_parent (klass);
+
+	/* Set the virtual methods. */
+
+	object_class->dispose = e_book_backend_sexp_dispose;
+}
+
+static void
+e_book_backend_sexp_init (EBookBackendSExp *sexp)
+{
+	EBookBackendSExpPrivate *priv;
+
+	priv             = g_new0 (EBookBackendSExpPrivate, 1);
+
+	sexp->priv = priv;
+	priv->search_context = g_new (SearchContext, 1);
+}
+
+/**
+ * e_book_backend_sexp_get_type:
+ */
+GType
+e_book_backend_sexp_get_type (void)
+{
+	static GType type = 0;
+
+	if (! type) {
+		GTypeInfo info = {
+			sizeof (EBookBackendSExpClass),
+			NULL, /* base_class_init */
+			NULL, /* base_class_finalize */
+			(GClassInitFunc)  e_book_backend_sexp_class_init,
+			NULL, /* class_finalize */
+			NULL, /* class_data */
+			sizeof (EBookBackendSExp),
+			0,    /* n_preallocs */
+			(GInstanceInitFunc) e_book_backend_sexp_init
+		};
+
+		type = g_type_register_static (G_TYPE_OBJECT, "EBookBackendSExp", &info, 0);
+	}
+
+	return type;
+}
Index: addressbook/libedata-book-dbus/e-book-backend-sexp.h
===================================================================
--- addressbook/libedata-book-dbus/e-book-backend-sexp.h	(revision 409)
+++ addressbook/libedata-book-dbus/e-book-backend-sexp.h	(working copy)
@@ -1 +1,60 @@
-link ../libedata-book-orbit/e-book-backend-sexp.h
\ No newline at end of file
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* 
+ * pas-backend-card-sexp.h
+ * Copyright 2000, 2001, Ximian, Inc.
+ *
+ * Authors:
+ *   Chris Lahey <clahey@ximian.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License, version 2, as published by the Free Software Foundation.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef __E_BOOK_BACKEND_SEXP_H__
+#define __E_BOOK_BACKEND_SEXP_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include <libebook/e-contact.h>
+#include <libedata-book/e-data-book-types.h>
+
+G_BEGIN_DECLS
+
+#define E_TYPE_BACKEND_SEXP        (e_book_backend_sexp_get_type ())
+#define E_BOOK_BACKEND_SEXP(o)          (G_TYPE_CHECK_INSTANCE_CAST ((o), E_TYPE_BACKEND_SEXP, EBookBackendSExp))
+#define E_BOOK_BACKEND_SEXP_CLASS(k)    (G_TYPE_CHECK_CLASS_CAST((k), E_BOOK_BACKEND_TYPE, EBookBackendSExpClass))
+#define E_IS_BACKEND_SEXP(o)       (G_TYPE_CHECK_INSTANCE_TYPE ((o), E_TYPE_BACKEND_SEXP))
+#define E_IS_BACKEND_SEXP_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), E_TYPE_BACKEND_SEXP))
+#define E_BOOK_BACKEND_SEXP_GET_CLASS(k) (G_TYPE_INSTANCE_GET_CLASS ((obj), E_TYPE_BACKEND_SEXP, EBookBackendSExpClass))
+
+typedef struct _EBookBackendSExpPrivate EBookBackendSExpPrivate;
+
+struct _EBookBackendSExp {
+	GObject parent_object;
+	EBookBackendSExpPrivate *priv;
+};
+
+struct _EBookBackendSExpClass {
+	GObjectClass parent_class;
+};
+
+EBookBackendSExp *e_book_backend_sexp_new      (const char *text);
+GType               e_book_backend_sexp_get_type (void);
+
+gboolean            e_book_backend_sexp_match_vcard (EBookBackendSExp *sexp, const char *vcard);
+gboolean            e_book_backend_sexp_match_contact (EBookBackendSExp *sexp, EContact *contact);
+
+G_END_DECLS
+
+#endif /* __E_BOOK_BACKEND_SEXP_H__ */
Index: COPYING
===================================================================
--- COPYING	(revision 409)
+++ COPYING	(working copy)
@@ -1,26 +1,23 @@
-		  GNU LIBRARY GENERAL PUBLIC LICENSE
+		    GNU GENERAL PUBLIC LICENSE
 		       Version 2, June 1991
 
- Copyright (C) 1991 Free Software Foundation, Inc.
-    		    59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  Everyone is permitted to copy and distribute verbatim copies
  of this license document, but changing it is not allowed.
 
-[This is the first released version of the library GPL.  It is
- numbered 2 because it goes with version 2 of the ordinary GPL.]
-
 			    Preamble
 
   The licenses for most software are designed to take away your
 freedom to share and change it.  By contrast, the GNU General Public
-Licenses are intended to guarantee your freedom to share and change
-free software--to make sure the software is free for all its users.
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
 
-  This license, the Library General Public License, applies to some
-specially designated Free Software Foundation software, and to any
-other libraries whose authors decide to use it.  You can use it for
-your libraries, too.
-
   When we speak of free software, we are referring to freedom, not
 price.  Our General Public Licenses are designed to make sure that you
 have the freedom to distribute copies of free software (and charge for
@@ -30,347 +27,195 @@
 
   To protect your rights, we need to make restrictions that forbid
 anyone to deny you these rights or to ask you to surrender the rights.
-These restrictions translate to certain responsibilities for you if
-you distribute copies of the library, or if you modify it.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
 
-  For example, if you distribute copies of the library, whether gratis
-or for a fee, you must give the recipients all the rights that we gave
-you.  You must make sure that they, too, receive or can get the source
-code.  If you link a program with the library, you must provide
-complete object files to the recipients so that they can relink them
-with the library, after making changes to the library and recompiling
-it.  And you must show them these terms so they know their rights.
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
 
-  Our method of protecting your rights has two steps: (1) copyright
-the library, and (2) offer you this license which gives you legal
-permission to copy, distribute and/or modify the library.
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
 
-  Also, for each distributor's protection, we want to make certain
+  Also, for each author's protection and ours, we want to make certain
 that everyone understands that there is no warranty for this free
-library.  If the library is modified by someone else and passed on, we
-want its recipients to know that what they have is not the original
-version, so that any problems introduced by others will not reflect on
-the original authors' reputations.
-
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
   Finally, any free program is threatened constantly by software
-patents.  We wish to avoid the danger that companies distributing free
-software will individually obtain patent licenses, thus in effect
-transforming the program into proprietary software.  To prevent this,
-we have made it clear that any patent must be licensed for everyone's
-free use or not licensed at all.
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
 
-  Most GNU software, including some libraries, is covered by the ordinary
-GNU General Public License, which was designed for utility programs.  This
-license, the GNU Library General Public License, applies to certain
-designated libraries.  This license is quite different from the ordinary
-one; be sure to read it in full, and don't assume that anything in it is
-the same as in the ordinary license.
-
-  The reason we have a separate public license for some libraries is that
-they blur the distinction we usually make between modifying or adding to a
-program and simply using it.  Linking a program with a library, without
-changing the library, is in some sense simply using the library, and is
-analogous to running a utility program or application program.  However, in
-a textual and legal sense, the linked executable is a combined work, a
-derivative of the original library, and the ordinary General Public License
-treats it as such.
-
-  Because of this blurred distinction, using the ordinary General
-Public License for libraries did not effectively promote software
-sharing, because most developers did not use the libraries.  We
-concluded that weaker conditions might promote sharing better.
-
-  However, unrestricted linking of non-free programs would deprive the
-users of those programs of all benefit from the free status of the
-libraries themselves.  This Library General Public License is intended to
-permit developers of non-free programs to use free libraries, while
-preserving your freedom as a user of such programs to change the free
-libraries that are incorporated in them.  (We have not seen how to achieve
-this as regards changes in header files, but we have achieved it as regards
-changes in the actual functions of the Library.)  The hope is that this
-will lead to faster development of free libraries.
-
   The precise terms and conditions for copying, distribution and
-modification follow.  Pay close attention to the difference between a
-"work based on the library" and a "work that uses the library".  The
-former contains code derived from the library, while the latter only
-works together with the library.
-
-  Note that it is possible for a library to be covered by the ordinary
-General Public License rather than by this special one.
+modification follow.
 
-		  GNU LIBRARY GENERAL PUBLIC LICENSE
+		    GNU GENERAL PUBLIC LICENSE
    TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 
-  0. This License Agreement applies to any software library which
-contains a notice placed by the copyright holder or other authorized
-party saying it may be distributed under the terms of this Library
-General Public License (also called "this License").  Each licensee is
-addressed as "you".
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
 
-  A "library" means a collection of software functions and/or data
-prepared so as to be conveniently linked with application programs
-(which use some of those functions and data) to form executables.
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
 
-  The "Library", below, refers to any such software library or work
-which has been distributed under these terms.  A "work based on the
-Library" means either the Library or any derivative work under
-copyright law: that is to say, a work containing the Library or a
-portion of it, either verbatim or with modifications and/or translated
-straightforwardly into another language.  (Hereinafter, translation is
-included without limitation in the term "modification".)
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
 
-  "Source code" for a work means the preferred form of the work for
-making modifications to it.  For a library, complete source code means
-all the source code for all modules it contains, plus any associated
-interface definition files, plus the scripts used to control compilation
-and installation of the library.
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
 
-  Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope.  The act of
-running a program using the Library is not restricted, and output from
-such a program is covered only if its contents constitute a work based
-on the Library (independent of the use of the Library in a tool for
-writing it).  Whether that is true depends on what the Library does
-and what the program that uses the Library does.
-  
-  1. You may copy and distribute verbatim copies of the Library's
-complete source code as you receive it, in any medium, provided that
-you conspicuously and appropriately publish on each copy an
-appropriate copyright notice and disclaimer of warranty; keep intact
-all the notices that refer to this License and to the absence of any
-warranty; and distribute a copy of this License along with the
-Library.
-
-  You may charge a fee for the physical act of transferring a copy,
-and you may at your option offer warranty protection in exchange for a
-fee.
-
-  2. You may modify your copy or copies of the Library or any portion
-of it, thus forming a work based on the Library, and copy and
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
 distribute such modifications or work under the terms of Section 1
 above, provided that you also meet all of these conditions:
 
-    a) The modified work must itself be a software library.
-
-    b) You must cause the files modified to carry prominent notices
+    a) You must cause the modified files to carry prominent notices
     stating that you changed the files and the date of any change.
 
-    c) You must cause the whole of the work to be licensed at no
-    charge to all third parties under the terms of this License.
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
 
-    d) If a facility in the modified Library refers to a function or a
-    table of data to be supplied by an application program that uses
-    the facility, other than as an argument passed when the facility
-    is invoked, then you must make a good faith effort to ensure that,
-    in the event an application does not supply such function or
-    table, the facility still operates, and performs whatever part of
-    its purpose remains meaningful.
-
-    (For example, a function in a library to compute square roots has
-    a purpose that is entirely well-defined independent of the
-    application.  Therefore, Subsection 2d requires that any
-    application-supplied function or table used by this function must
-    be optional: if the application does not supply it, the square
-    root function must still compute square roots.)
-
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
 These requirements apply to the modified work as a whole.  If
-identifiable sections of that work are not derived from the Library,
+identifiable sections of that work are not derived from the Program,
 and can be reasonably considered independent and separate works in
 themselves, then this License, and its terms, do not apply to those
 sections when you distribute them as separate works.  But when you
 distribute the same sections as part of a whole which is a work based
-on the Library, the distribution of the whole must be on the terms of
+on the Program, the distribution of the whole must be on the terms of
 this License, whose permissions for other licensees extend to the
-entire whole, and thus to each and every part regardless of who wrote
-it.
+entire whole, and thus to each and every part regardless of who wrote it.
 
 Thus, it is not the intent of this section to claim rights or contest
 your rights to work written entirely by you; rather, the intent is to
 exercise the right to control the distribution of derivative or
-collective works based on the Library.
+collective works based on the Program.
 
-In addition, mere aggregation of another work not based on the Library
-with the Library (or with a work based on the Library) on a volume of
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
 a storage or distribution medium does not bring the other work under
 the scope of this License.
 
-  3. You may opt to apply the terms of the ordinary GNU General Public
-License instead of this License to a given copy of the Library.  To do
-this, you must alter all the notices that refer to this License, so
-that they refer to the ordinary GNU General Public License, version 2,
-instead of to this License.  (If a newer version than version 2 of the
-ordinary GNU General Public License has appeared, then you can specify
-that version instead if you wish.)  Do not make any other change in
-these notices.
-
-  Once this change is made in a given copy, it is irreversible for
-that copy, so the ordinary GNU General Public License applies to all
-subsequent copies and derivative works made from that copy.
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
 
-  This option is useful when you wish to copy part of the code of
-the Library into a program that is not a library.
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
 
-  4. You may copy and distribute the Library (or a portion or
-derivative of it, under Section 2) in object code or executable form
-under the terms of Sections 1 and 2 above provided that you accompany
-it with the complete corresponding machine-readable source code, which
-must be distributed under the terms of Sections 1 and 2 above on a
-medium customarily used for software interchange.
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
 
-  If distribution of object code is made by offering access to copy
-from a designated place, then offering equivalent access to copy the
-source code from the same place satisfies the requirement to
-distribute the source code, even though third parties are not
-compelled to copy the source along with the object code.
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
 
-  5. A program that contains no derivative of any portion of the
-Library, but is designed to work with the Library by being compiled or
-linked with it, is called a "work that uses the Library".  Such a
-work, in isolation, is not a derivative work of the Library, and
-therefore falls outside the scope of this License.
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
 
-  However, linking a "work that uses the Library" with the Library
-creates an executable that is a derivative of the Library (because it
-contains portions of the Library), rather than a "work that uses the
-library".  The executable is therefore covered by this License.
-Section 6 states terms for distribution of such executables.
-
-  When a "work that uses the Library" uses material from a header file
-that is part of the Library, the object code for the work may be a
-derivative work of the Library even though the source code is not.
-Whether this is true is especially significant if the work can be
-linked without the Library, or if the work is itself a library.  The
-threshold for this to be true is not precisely defined by law.
-
-  If such an object file uses only numerical parameters, data
-structure layouts and accessors, and small macros and small inline
-functions (ten lines or less in length), then the use of the object
-file is unrestricted, regardless of whether it is legally a derivative
-work.  (Executables containing this object code plus portions of the
-Library will still fall under Section 6.)
-
-  Otherwise, if the work is a derivative of the Library, you may
-distribute the object code for the work under the terms of Section 6.
-Any executables containing that work also fall under Section 6,
-whether or not they are linked directly with the Library itself.
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
 
-  6. As an exception to the Sections above, you may also compile or
-link a "work that uses the Library" with the Library to produce a
-work containing portions of the Library, and distribute that work
-under terms of your choice, provided that the terms permit
-modification of the work for the customer's own use and reverse
-engineering for debugging such modifications.
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
 
-  You must give prominent notice with each copy of the work that the
-Library is used in it and that the Library and its use are covered by
-this License.  You must supply a copy of this License.  If the work
-during execution displays copyright notices, you must include the
-copyright notice for the Library among them, as well as a reference
-directing the user to the copy of this License.  Also, you must do one
-of these things:
-
-    a) Accompany the work with the complete corresponding
-    machine-readable source code for the Library including whatever
-    changes were used in the work (which must be distributed under
-    Sections 1 and 2 above); and, if the work is an executable linked
-    with the Library, with the complete machine-readable "work that
-    uses the Library", as object code and/or source code, so that the
-    user can modify the Library and then relink to produce a modified
-    executable containing the modified Library.  (It is understood
-    that the user who changes the contents of definitions files in the
-    Library will not necessarily be able to recompile the application
-    to use the modified definitions.)
-
-    b) Accompany the work with a written offer, valid for at
-    least three years, to give the same user the materials
-    specified in Subsection 6a, above, for a charge no more
-    than the cost of performing this distribution.
-
-    c) If distribution of the work is made by offering access to copy
-    from a designated place, offer equivalent access to copy the above
-    specified materials from the same place.
-
-    d) Verify that the user has already received a copy of these
-    materials or that you have already sent this user a copy.
-
-  For an executable, the required form of the "work that uses the
-Library" must include any data and utility programs needed for
-reproducing the executable from it.  However, as a special exception,
-the source code distributed need not include anything that is normally
-distributed (in either source or binary form) with the major
-components (compiler, kernel, and so on) of the operating system on
-which the executable runs, unless that component itself accompanies
-the executable.
-
-  It may happen that this requirement contradicts the license
-restrictions of other proprietary libraries that do not normally
-accompany the operating system.  Such a contradiction means you cannot
-use both them and the Library together in an executable that you
-distribute.
-
-  7. You may place library facilities that are a work based on the
-Library side-by-side in a single library together with other library
-facilities not covered by this License, and distribute such a combined
-library, provided that the separate distribution of the work based on
-the Library and of the other library facilities is otherwise
-permitted, and provided that you do these two things:
-
-    a) Accompany the combined library with a copy of the same work
-    based on the Library, uncombined with any other library
-    facilities.  This must be distributed under the terms of the
-    Sections above.
-
-    b) Give prominent notice with the combined library of the fact
-    that part of it is a work based on the Library, and explaining
-    where to find the accompanying uncombined form of the same work.
-
-  8. You may not copy, modify, sublicense, link with, or distribute
-the Library except as expressly provided under this License.  Any
-attempt otherwise to copy, modify, sublicense, link with, or
-distribute the Library is void, and will automatically terminate your
-rights under this License.  However, parties who have received copies,
-or rights, from you under this License will not have their licenses
-terminated so long as such parties remain in full compliance.
-
-  9. You are not required to accept this License, since you have not
+  5. You are not required to accept this License, since you have not
 signed it.  However, nothing else grants you permission to modify or
-distribute the Library or its derivative works.  These actions are
+distribute the Program or its derivative works.  These actions are
 prohibited by law if you do not accept this License.  Therefore, by
-modifying or distributing the Library (or any work based on the
-Library), you indicate your acceptance of this License to do so, and
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
 all its terms and conditions for copying, distributing or modifying
-the Library or works based on it.
+the Program or works based on it.
 
-  10. Each time you redistribute the Library (or any work based on the
-Library), the recipient automatically receives a license from the
-original licensor to copy, distribute, link with or modify the Library
-subject to these terms and conditions.  You may not impose any further
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
 restrictions on the recipients' exercise of the rights granted herein.
 You are not responsible for enforcing compliance by third parties to
 this License.
-
-  11. If, as a consequence of a court judgment or allegation of patent
+
+  7. If, as a consequence of a court judgment or allegation of patent
 infringement or for any other reason (not limited to patent issues),
 conditions are imposed on you (whether by court order, agreement or
 otherwise) that contradict the conditions of this License, they do not
 excuse you from the conditions of this License.  If you cannot
 distribute so as to satisfy simultaneously your obligations under this
 License and any other pertinent obligations, then as a consequence you
-may not distribute the Library at all.  For example, if a patent
-license would not permit royalty-free redistribution of the Library by
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
 all those who receive copies directly or indirectly through you, then
 the only way you could satisfy both it and this License would be to
-refrain entirely from distribution of the Library.
+refrain entirely from distribution of the Program.
 
-If any portion of this section is held invalid or unenforceable under any
-particular circumstance, the balance of the section is intended to apply,
-and the section as a whole is intended to apply in other circumstances.
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
 
 It is not the purpose of this section to induce you to infringe any
 patents or other property right claims or to contest validity of any
 such claims; this section has the sole purpose of protecting the
-integrity of the free software distribution system which is
+integrity of the free software distribution system, which is
 implemented by public license practices.  Many people have made
 generous contributions to the wide range of software distributed
 through that system in reliance on consistent application of that
@@ -380,103 +225,116 @@
 
 This section is intended to make thoroughly clear what is believed to
 be a consequence of the rest of this License.
-
-  12. If the distribution and/or use of the Library is restricted in
+
+  8. If the distribution and/or use of the Program is restricted in
 certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Library under this License may add
-an explicit geographical distribution limitation excluding those countries,
-so that distribution is permitted only in or among countries not thus
-excluded.  In such case, this License incorporates the limitation as if
-written in the body of this License.
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
 
-  13. The Free Software Foundation may publish revised and/or new
-versions of the Library General Public License from time to time.
-Such new versions will be similar in spirit to the present version,
-but may differ in detail to address new problems or concerns.
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
 
-Each version is given a distinguishing version number.  If the Library
-specifies a version number of this License which applies to it and
-"any later version", you have the option of following the terms and
-conditions either of that version or of any later version published by
-the Free Software Foundation.  If the Library does not specify a
-license version number, you may choose any version ever published by
-the Free Software Foundation.
-
-  14. If you wish to incorporate parts of the Library into other free
-programs whose distribution conditions are incompatible with these,
-write to the author to ask for permission.  For software which is
-copyrighted by the Free Software Foundation, write to the Free
-Software Foundation; we sometimes make exceptions for this.  Our
-decision will be guided by the two goals of preserving the free status
-of all derivatives of our free software and of promoting the sharing
-and reuse of software generally.
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
 
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
 			    NO WARRANTY
 
-  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
-WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
-EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
-OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
-KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
-LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
-THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
 
-  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
-WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
-AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
-FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
-CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
-LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
-RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
-FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
-SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
-DAMAGES.
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
 
 		     END OF TERMS AND CONDITIONS
 
-           How to Apply These Terms to Your New Libraries
+	    How to Apply These Terms to Your New Programs
 
-  If you develop a new library, and you want it to be of the greatest
-possible use to the public, we recommend making it free software that
-everyone can redistribute and change.  You can do so by permitting
-redistribution under these terms (or, alternatively, under the terms of the
-ordinary General Public License).
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
 
-  To apply these terms, attach the following notices to the library.  It is
-safest to attach them to the start of each source file to most effectively
-convey the exclusion of warranty; and each file should have at least the
-"copyright" line and a pointer to where the full notice is found.
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
 
-    <one line to give the library's name and a brief idea of what it does.>
+    <one line to give the program's name and a brief idea of what it does.>
     Copyright (C) <year>  <name of author>
 
-    This library is free software; you can redistribute it and/or
-    modify it under the terms of the GNU Library 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 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 library is distributed in the hope that it will be useful,
+    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
-    Library General Public License for more details.
+    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 Library General Public
-    License along with this library; if not, write to the 
-    Free Software Foundation, Inc., 59 Temple Place - Suite 330, 
-    Boston, MA  02111-1307  USA.
+    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 Place, Suite 330, Boston, MA  02111-1307  USA
 
+
 Also add information on how to contact you by electronic and paper mail.
 
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year  name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
 You should also get your employer (if you work as a programmer) or your
-school, if any, to sign a "copyright disclaimer" for the library, if
+school, if any, to sign a "copyright disclaimer" for the program, if
 necessary.  Here is a sample; alter the names:
 
-  Yoyodyne, Inc., hereby disclaims all copyright interest in the
-  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
 
-  <signature of Ty Coon>, 1 April 1990
+  <signature of Ty Coon>, 1 April 1989
   Ty Coon, President of Vice
 
-That's all there is to it!
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
Index: configure.in
===================================================================
--- configure.in	(revision 409)
+++ configure.in	(working copy)
@@ -1104,7 +1104,7 @@
 	fi
 fi
 
-GLIB_REQUIRED="2.9.1"
+GLIB_REQUIRED="2.8.0"
 LIBBONOBO_REQUIRED="2.4.2"
 ORBIT_REQUIRED="2.9.8"
 
Index: calendar/libical/src/libicalss/icalssyacc.h
===================================================================
--- calendar/libical/src/libicalss/icalssyacc.h	(revision 409)
+++ calendar/libical/src/libicalss/icalssyacc.h	(working copy)
@@ -1,7 +1,7 @@
-/* A Bison parser, made by GNU Bison 1.875d.  */
+/* A Bison parser, made by GNU Bison 1.875a.  */
 
 /* Skeleton parser for Yacc-like parsing with Bison,
-   Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004 Free Software Foundation, Inc.
+   Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003 Free Software Foundation, Inc.
 
    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
@@ -78,7 +78,7 @@
 typedef union YYSTYPE {
 	char* v_string;
 } YYSTYPE;
-/* Line 1285 of yacc.c.  */
+/* Line 1240 of yacc.c.  */
 #line 83 "icalssyacc.h"
 # define yystype YYSTYPE /* obsolescent; will be withdrawn */
 # define YYSTYPE_IS_DECLARED 1
Index: calendar/libical/INSTALL
===================================================================
--- calendar/libical/INSTALL	(revision 409)
+++ calendar/libical/INSTALL	(working copy)
@@ -1,4 +1,4 @@
-Copyright 1994, 1995, 1996, 1999, 2000, 2001, 2002 Free Software
+Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002 Free Software
 Foundation, Inc.
 
    This file is free documentation; the Free Software Foundation gives
Index: calendar/libecal-dbus/e-cal-recur.c
===================================================================
--- calendar/libecal-dbus/e-cal-recur.c	(revision 409)
+++ calendar/libecal-dbus/e-cal-recur.c	(working copy)
@@ -1 +1,4067 @@
-link ../libecal/e-cal-recur.c
\ No newline at end of file
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Evolution calendar recurrence rule functions
+ *
+ * Copyright (C) 2000 Ximian, Inc.
+ *
+ * Author: Damon Chaplin <damon@ximian.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include "e-cal-recur.h"
+#include "e-cal-time-util.h"
+
+
+/*
+ * Introduction to The Recurrence Generation Functions:
+ *
+ * Note: This is pretty complicated. See the iCalendar spec (RFC 2445) for
+ *       the specification of the recurrence rules and lots of examples
+ *	 (sections 4.3.10 & 4.8.5). We also want to support the older
+ *	 vCalendar spec, though this should be easy since it is basically a
+ *	 subset of iCalendar.
+ *
+ * o An iCalendar event can have any number of recurrence rules specifying
+ *   occurrences of the event, as well as dates & times of specific
+ *   occurrences. It can also have any number of recurrence rules and
+ *   specific dates & times specifying exceptions to the occurrences.
+ *   So we first merge all the occurrences generated, eliminating any
+ *   duplicates, then we generate all the exceptions and remove these to
+ *   form the final set of occurrences.
+ *
+ * o There are 7 frequencies of occurrences: YEARLY, MONTHLY, WEEKLY, DAILY,
+ *   HOURLY, MINUTELY & SECONDLY. The 'interval' property specifies the
+ *   multiples of the frequency which we step by. We generate a 'set' of
+ *   occurrences for each period defined by the frequency & interval.
+ *   So for a YEARLY frequency with an interval of 3, we generate a set of
+ *   occurrences for every 3rd year. We use complete years here -  any
+ *   generated occurrences that occur before the event's start (or after its
+ *   end) are just discarded.
+ *
+ * o There are 8 frequency modifiers: BYMONTH, BYWEEKNO, BYYEARDAY, BYMONTHDAY,
+ *   BYDAY, BYHOUR, BYMINUTE & BYSECOND. These can either add extra occurrences
+ *   or filter out occurrences. For example 'FREQ=YEARLY;BYMONTH=1,2' produces
+ *   2 occurrences for each year rather than the default 1. And
+ *   'FREQ=DAILY;BYMONTH=1' filters out all occurrences except those in Jan.
+ *   If the modifier works on periods which are less than the recurrence
+ *   frequency, then extra occurrences are added, otherwise occurrences are
+ *   filtered. So we have 2 functions for each modifier - one to expand events
+ *   and the other to filter. We use a table of functions for each frequency
+ *   which points to the appropriate function to use for each modifier.
+ *
+ * o Any number of frequency modifiers can be used in a recurrence rule.
+ *   (Though the iCalendar spec says that BYWEEKNO can only be used in a YEARLY
+ *   rule, and some modifiers aren't appropriate for some frequencies - e.g.
+ *   BYMONTHDAY is not really useful in a WEEKLY frequency, and BYYEARDAY is
+ *   not useful in a MONTHLY or WEEKLY frequency).
+ *   The frequency modifiers are applied in the order given above. The first 5
+ *   modifier rules (BYMONTH, BYWEEKNO, BYYEARDAY, BYMONTHDAY & BYDAY) all
+ *   produce the days on which the occurrences take place, and so we have to
+ *   compute some of these in parallel rather than sequentially, or we may end
+ *   up with too many days.
+ *
+ * o Note that some expansion functions may produce days which are invalid,
+ *   e.g. 31st September, 30th Feb. These invalid days are removed before the
+ *   BYHOUR, BYMINUTE & BYSECOND modifier functions are applied.
+ *
+ * o After the set of occurrences for the frequency interval are generated,
+ *   the BYSETPOS property is used to select which of the occurrences are
+ *   finally output. If BYSETPOS is not specified then all the occurrences are
+ *   output.
+ *
+ *
+ * FIXME: I think there are a few errors in this code:
+ *
+ *  1) I'm not sure it should be generating events in parallel like it says
+ *     above. That needs to be checked.
+ *
+ *  2) I didn't think about timezone changes when implementing this. I just
+ *     assumed all the occurrences of the event would be in local time.
+ *     But when clocks go back or forwards due to daylight-saving time, some
+ *     special handling may be needed, especially for the shorter frequencies.
+ *     e.g. for a MINUTELY frequency it should probably iterate over all the
+ *     minutes before and after clocks go back (i.e. some may be the same local
+ *     time but have different UTC offsets). For longer frequencies, if an
+ *     occurrence lands on the overlapping or non-existant time when clocks
+ *     go back/forward, then it may need to choose which of the times to use
+ *     or move the time forward or something. I'm not sure this is clear in the
+ *     spec.
+ */
+
+/* This is the maximum year we will go up to (inclusive). Since we use time_t
+   values we can't go past 2037 anyway, and some of our VTIMEZONEs may stop
+   at 2037 as well. */
+#define MAX_YEAR	2037
+
+/* Define this for some debugging output. */
+#if 0
+#define CAL_OBJ_DEBUG	1
+#endif
+
+/* We will use icalrecurrencetype instead of this eventually. */
+typedef struct {
+	icalrecurrencetype_frequency freq;
+
+	int            interval;
+
+	/* Specifies the end of the recurrence, inclusive. No occurrences are
+	   generated after this date. If it is 0, the event recurs forever. */
+	time_t         enddate;
+
+	/* WKST property - the week start day: 0 = Monday to 6 = Sunday. */
+	gint	       week_start_day;
+
+
+	/* NOTE: I've used GList's here, but it doesn't matter if we use
+	   other data structures like arrays. The code should be easy to
+	   change. So long as it is easy to see if the modifier is set. */
+
+	/* For BYMONTH modifier. A list of GINT_TO_POINTERs, 0-11. */
+	GList	      *bymonth;
+
+	/* For BYWEEKNO modifier. A list of GINT_TO_POINTERs, [+-]1-53. */
+	GList	      *byweekno;
+
+	/* For BYYEARDAY modifier. A list of GINT_TO_POINTERs, [+-]1-366. */
+	GList	      *byyearday;
+
+	/* For BYMONTHDAY modifier. A list of GINT_TO_POINTERs, [+-]1-31. */
+	GList	      *bymonthday;
+
+	/* For BYDAY modifier. A list of GINT_TO_POINTERs, in pairs.
+	   The first of each pair is the weekday, 0 = Monday to 6 = Sunday.
+	   The second of each pair is the week number [+-]0-53. */
+	GList	      *byday;
+
+	/* For BYHOUR modifier. A list of GINT_TO_POINTERs, 0-23. */
+	GList	      *byhour;
+
+	/* For BYMINUTE modifier. A list of GINT_TO_POINTERs, 0-59. */
+	GList	      *byminute;
+
+	/* For BYSECOND modifier. A list of GINT_TO_POINTERs, 0-60. */
+	GList	      *bysecond;
+
+	/* For BYSETPOS modifier. A list of GINT_TO_POINTERs, +ve or -ve. */
+	GList	      *bysetpos;
+} ECalRecurrence;
+
+/* This is what we use to pass to all the filter functions. */
+typedef struct _RecurData RecurData;
+struct _RecurData {
+	ECalRecurrence *recur;
+
+	/* This is used for the WEEKLY frequency. It is the offset from the
+	   week_start_day. */
+	gint weekday_offset;
+
+	/* This is used for fast lookup in BYMONTH filtering. */
+	guint8 months[12];
+
+	/* This is used for fast lookup in BYYEARDAY filtering. */
+	guint8 yeardays[367], neg_yeardays[367]; /* Days are 1 - 366. */
+
+	/* This is used for fast lookup in BYMONTHDAY filtering. */
+	guint8 monthdays[32], neg_monthdays[32]; /* Days are 1 to 31. */
+
+	/* This is used for fast lookup in BYDAY filtering. */
+	guint8 weekdays[7];
+
+	/* This is used for fast lookup in BYHOUR filtering. */
+	guint8 hours[24];
+
+	/* This is used for fast lookup in BYMINUTE filtering. */
+	guint8 minutes[60];
+
+	/* This is used for fast lookup in BYSECOND filtering. */
+	guint8 seconds[62];
+};
+
+/* This is what we use to represent a date & time. */
+typedef struct _CalObjTime CalObjTime;
+struct _CalObjTime {
+	guint16 year;
+	guint8 month;		/* 0 - 11 */
+	guint8 day;		/* 1 - 31 */
+	guint8 hour;		/* 0 - 23 */
+	guint8 minute;		/* 0 - 59 */
+	guint8 second;		/* 0 - 59 (maybe up to 61 for leap seconds) */
+	guint8 flags;		/* The meaning of this depends on where the
+				   CalObjTime is used. In most cases this is
+				   set to TRUE to indicate that this is an
+				   RDATE with an end or a duration set.
+				   In the exceptions code, this is set to TRUE
+				   to indicate that this is an EXDATE with a
+				   DATE value. */
+};
+
+/* This is what we use to represent specific recurrence dates.
+   Note that we assume it starts with a CalObjTime when sorting. */
+typedef struct _CalObjRecurrenceDate CalObjRecurrenceDate;
+struct _CalObjRecurrenceDate {
+	CalObjTime start;
+	ECalComponentPeriod *period;
+};
+
+/* The paramter we use to store the enddate in RRULE and EXRULE properties. */
+#define EVOLUTION_END_DATE_PARAMETER	"X-EVOLUTION-ENDDATE"
+
+typedef gboolean (*CalObjFindStartFn) (CalObjTime *event_start,
+				       CalObjTime *event_end,
+				       RecurData  *recur_data,
+				       CalObjTime *interval_start,
+				       CalObjTime *interval_end,
+				       CalObjTime *cotime);
+typedef gboolean (*CalObjFindNextFn)  (CalObjTime *cotime,
+				       CalObjTime *event_end,
+				       RecurData  *recur_data,
+				       CalObjTime *interval_end);
+typedef GArray*	 (*CalObjFilterFn)    (RecurData  *recur_data,
+				       GArray     *occs);
+
+typedef struct _ECalRecurVTable ECalRecurVTable;
+struct _ECalRecurVTable {
+	CalObjFindStartFn find_start_position;
+	CalObjFindNextFn find_next_position;
+
+	CalObjFilterFn bymonth_filter;
+	CalObjFilterFn byweekno_filter;
+	CalObjFilterFn byyearday_filter;
+	CalObjFilterFn bymonthday_filter;
+	CalObjFilterFn byday_filter;
+	CalObjFilterFn byhour_filter;
+	CalObjFilterFn byminute_filter;
+	CalObjFilterFn bysecond_filter;
+};
+
+
+/* This is used to specify which parts of the CalObjTime to compare in
+   cal_obj_time_compare(). */
+typedef enum {
+	CALOBJ_YEAR,
+	CALOBJ_MONTH,
+	CALOBJ_DAY,
+	CALOBJ_HOUR,
+	CALOBJ_MINUTE,
+	CALOBJ_SECOND
+} CalObjTimeComparison;
+
+static void e_cal_recur_generate_instances_of_rule (ECalComponent	*comp,
+						  icalproperty	*prop,
+						  time_t	 start,
+						  time_t	 end,
+						  ECalRecurInstanceFn cb,
+						  gpointer       cb_data,
+						  ECalRecurResolveTimezoneFn  tz_cb,
+						  gpointer	 tz_cb_data,
+						  icaltimezone	*default_timezone);
+
+static ECalRecurrence * e_cal_recur_from_icalproperty (icalproperty *prop,
+						    gboolean exception,
+						    icaltimezone *zone,
+						    gboolean convert_end_date);
+static gint e_cal_recur_ical_weekday_to_weekday	(enum icalrecurrencetype_weekday day);
+static void	e_cal_recur_free			(ECalRecurrence	*r);
+
+
+static gboolean cal_object_get_rdate_end	(CalObjTime	*occ,
+						 GArray		*rdate_periods);
+static void	cal_object_compute_duration	(CalObjTime	*start,
+						 CalObjTime	*end,
+						 gint		*days,
+						 gint		*seconds);
+
+static gboolean generate_instances_for_chunk	(ECalComponent		*comp,
+						 time_t			 comp_dtstart,
+						 icaltimezone		*zone,
+						 GSList			*rrules,
+						 GSList			*rdates,
+						 GSList			*exrules,
+						 GSList			*exdates,
+						 gboolean		 single_rule,
+						 CalObjTime		*event_start,
+						 time_t			 interval_start,
+						 CalObjTime		*chunk_start,
+						 CalObjTime		*chunk_end,
+						 gint			 duration_days,
+						 gint			 duration_seconds,
+						 gboolean		 convert_end_date,
+						 ECalRecurInstanceFn	 cb,
+						 gpointer		 cb_data);
+
+static GArray* cal_obj_expand_recurrence	(CalObjTime	  *event_start,
+						 icaltimezone	  *zone,
+						 ECalRecurrence	  *recur,
+						 CalObjTime	  *interval_start,
+						 CalObjTime	  *interval_end,
+						 gboolean	  *finished);
+
+static GArray*	cal_obj_generate_set_yearly	(RecurData	*recur_data,
+						 ECalRecurVTable *vtable,
+						 CalObjTime	*occ);
+static GArray*	cal_obj_generate_set_monthly	(RecurData	*recur_data,
+						 ECalRecurVTable *vtable,
+						 CalObjTime	*occ);
+static GArray*	cal_obj_generate_set_default	(RecurData	*recur_data,
+						 ECalRecurVTable *vtable,
+						 CalObjTime	*occ);
+
+
+static ECalRecurVTable* cal_obj_get_vtable	(icalrecurrencetype_frequency recur_type);
+static void	cal_obj_initialize_recur_data	(RecurData	*recur_data,
+						 ECalRecurrence	*recur,
+						 CalObjTime	*event_start);
+static void	cal_obj_sort_occurrences	(GArray		*occs);
+static gint	cal_obj_time_compare_func	(const void	*arg1,
+						 const void	*arg2);
+static void	cal_obj_remove_duplicates_and_invalid_dates (GArray	*occs);
+static void	cal_obj_remove_exceptions	(GArray		*occs,
+						 GArray		*ex_occs);
+static GArray*	cal_obj_bysetpos_filter		(ECalRecurrence	*recur,
+						 GArray		*occs);
+
+
+static gboolean cal_obj_yearly_find_start_position (CalObjTime *event_start,
+						    CalObjTime *event_end,
+						    RecurData  *recur_data,
+						    CalObjTime *interval_start,
+						    CalObjTime *interval_end,
+						    CalObjTime *cotime);
+static gboolean cal_obj_yearly_find_next_position  (CalObjTime *cotime,
+						    CalObjTime *event_end,
+						    RecurData  *recur_data,
+						    CalObjTime *interval_end);
+
+static gboolean cal_obj_monthly_find_start_position (CalObjTime *event_start,
+						     CalObjTime *event_end,
+						     RecurData  *recur_data,
+						     CalObjTime *interval_start,
+						     CalObjTime *interval_end,
+						     CalObjTime *cotime);
+static gboolean cal_obj_monthly_find_next_position  (CalObjTime *cotime,
+						     CalObjTime *event_end,
+						     RecurData  *recur_data,
+						     CalObjTime *interval_end);
+
+static gboolean cal_obj_weekly_find_start_position (CalObjTime *event_start,
+						    CalObjTime *event_end,
+						    RecurData  *recur_data,
+						    CalObjTime *interval_start,
+						    CalObjTime *interval_end,
+						    CalObjTime *cotime);
+static gboolean cal_obj_weekly_find_next_position  (CalObjTime *cotime,
+						    CalObjTime *event_end,
+						    RecurData  *recur_data,
+						    CalObjTime *interval_end);
+
+static gboolean cal_obj_daily_find_start_position  (CalObjTime *event_start,
+						    CalObjTime *event_end,
+						    RecurData  *recur_data,
+						    CalObjTime *interval_start,
+						    CalObjTime *interval_end,
+						    CalObjTime *cotime);
+static gboolean cal_obj_daily_find_next_position   (CalObjTime *cotime,
+						    CalObjTime *event_end,
+						    RecurData  *recur_data,
+						    CalObjTime *interval_end);
+
+static gboolean cal_obj_hourly_find_start_position (CalObjTime *event_start,
+						    CalObjTime *event_end,
+						    RecurData  *recur_data,
+						    CalObjTime *interval_start,
+						    CalObjTime *interval_end,
+						    CalObjTime *cotime);
+static gboolean cal_obj_hourly_find_next_position  (CalObjTime *cotime,
+						    CalObjTime *event_end,
+						    RecurData  *recur_data,
+						    CalObjTime *interval_end);
+
+static gboolean cal_obj_minutely_find_start_position (CalObjTime *event_start,
+						      CalObjTime *event_end,
+						      RecurData  *recur_data,
+						      CalObjTime *interval_start,
+						      CalObjTime *interval_end,
+						      CalObjTime *cotime);
+static gboolean cal_obj_minutely_find_next_position  (CalObjTime *cotime,
+						      CalObjTime *event_end,
+						      RecurData  *recur_data,
+						      CalObjTime *interval_end);
+
+static gboolean cal_obj_secondly_find_start_position (CalObjTime *event_start,
+						      CalObjTime *event_end,
+						      RecurData  *recur_data,
+						      CalObjTime *interval_start,
+						      CalObjTime *interval_end,
+						      CalObjTime *cotime);
+static gboolean cal_obj_secondly_find_next_position  (CalObjTime *cotime,
+						      CalObjTime *event_end,
+						      RecurData  *recur_data,
+						      CalObjTime *interval_end);
+
+static GArray* cal_obj_bymonth_expand		(RecurData  *recur_data,
+						 GArray     *occs);
+static GArray* cal_obj_bymonth_filter		(RecurData  *recur_data,
+						 GArray     *occs);
+static GArray* cal_obj_byweekno_expand		(RecurData  *recur_data,
+						 GArray     *occs);
+#if 0
+/* This isn't used at present. */
+static GArray* cal_obj_byweekno_filter		(RecurData  *recur_data,
+						 GArray     *occs);
+#endif
+static GArray* cal_obj_byyearday_expand		(RecurData  *recur_data,
+						 GArray     *occs);
+static GArray* cal_obj_byyearday_filter		(RecurData  *recur_data,
+						 GArray     *occs);
+static GArray* cal_obj_bymonthday_expand	(RecurData  *recur_data,
+						 GArray     *occs);
+static GArray* cal_obj_bymonthday_filter	(RecurData  *recur_data,
+						 GArray     *occs);
+static GArray* cal_obj_byday_expand_yearly	(RecurData  *recur_data,
+						 GArray     *occs);
+static GArray* cal_obj_byday_expand_monthly	(RecurData  *recur_data,
+						 GArray     *occs);
+static GArray* cal_obj_byday_expand_weekly	(RecurData  *recur_data,
+						 GArray     *occs);
+static GArray* cal_obj_byday_filter		(RecurData  *recur_data,
+						 GArray     *occs);
+static GArray* cal_obj_byhour_expand		(RecurData  *recur_data,
+						 GArray     *occs);
+static GArray* cal_obj_byhour_filter		(RecurData  *recur_data,
+						 GArray     *occs);
+static GArray* cal_obj_byminute_expand		(RecurData  *recur_data,
+						 GArray     *occs);
+static GArray* cal_obj_byminute_filter		(RecurData  *recur_data,
+						 GArray     *occs);
+static GArray* cal_obj_bysecond_expand		(RecurData  *recur_data,
+						 GArray     *occs);
+static GArray* cal_obj_bysecond_filter		(RecurData  *recur_data,
+						 GArray     *occs);
+
+static void cal_obj_time_add_months		(CalObjTime *cotime,
+						 gint	     months);
+static void cal_obj_time_add_days		(CalObjTime *cotime,
+						 gint	     days);
+static void cal_obj_time_add_hours		(CalObjTime *cotime,
+						 gint	     hours);
+static void cal_obj_time_add_minutes		(CalObjTime *cotime,
+						 gint	     minutes);
+static void cal_obj_time_add_seconds		(CalObjTime *cotime,
+						 gint	     seconds);
+static gint cal_obj_time_compare		(CalObjTime *cotime1,
+						 CalObjTime *cotime2,
+						 CalObjTimeComparison type);
+static gint cal_obj_time_weekday		(CalObjTime *cotime);
+static gint cal_obj_time_weekday_offset		(CalObjTime *cotime,
+						 ECalRecurrence *recur);
+static gint cal_obj_time_day_of_year		(CalObjTime *cotime);
+static void cal_obj_time_find_first_week	(CalObjTime *cotime,
+						 RecurData  *recur_data);
+static void cal_object_time_from_time		(CalObjTime *cotime,
+						 time_t      t,
+						 icaltimezone *zone);
+static gint cal_obj_date_only_compare_func	(const void *arg1,
+						 const void *arg2);
+
+
+
+static gboolean e_cal_recur_ensure_end_dates	(ECalComponent	*comp,
+						 gboolean	 refresh,
+						 ECalRecurResolveTimezoneFn tz_cb,
+						 gpointer	 tz_cb_data);
+static gboolean e_cal_recur_ensure_rule_end_date	(ECalComponent	*comp,
+						 icalproperty	*prop,
+						 gboolean	 exception,
+						 gboolean	 refresh,
+						 ECalRecurResolveTimezoneFn tz_cb,
+						 gpointer	 tz_cb_data);
+static gboolean e_cal_recur_ensure_rule_end_date_cb	(ECalComponent	*comp,
+							 time_t		 instance_start,
+							 time_t		 instance_end,
+							 gpointer	 data);
+static time_t e_cal_recur_get_rule_end_date	(icalproperty	*prop,
+						 icaltimezone	*default_timezone);
+static void e_cal_recur_set_rule_end_date		(icalproperty	*prop,
+						 time_t		 end_date);
+
+
+#ifdef CAL_OBJ_DEBUG
+static char* cal_obj_time_to_string		(CalObjTime	*cotime);
+#endif
+
+
+ECalRecurVTable cal_obj_yearly_vtable = {
+	cal_obj_yearly_find_start_position,
+	cal_obj_yearly_find_next_position,
+
+	cal_obj_bymonth_expand,
+	cal_obj_byweekno_expand,
+	cal_obj_byyearday_expand,
+	cal_obj_bymonthday_expand,
+	cal_obj_byday_expand_yearly,
+	cal_obj_byhour_expand,
+	cal_obj_byminute_expand,
+	cal_obj_bysecond_expand
+};
+
+ECalRecurVTable cal_obj_monthly_vtable = {
+	cal_obj_monthly_find_start_position,
+	cal_obj_monthly_find_next_position,
+
+	cal_obj_bymonth_filter,
+	NULL, /* BYWEEKNO is only applicable to YEARLY frequency. */
+	NULL, /* BYYEARDAY is not useful in a MONTHLY frequency. */
+	cal_obj_bymonthday_expand,
+	cal_obj_byday_expand_monthly,
+	cal_obj_byhour_expand,
+	cal_obj_byminute_expand,
+	cal_obj_bysecond_expand
+};
+
+ECalRecurVTable cal_obj_weekly_vtable = {
+	cal_obj_weekly_find_start_position,
+	cal_obj_weekly_find_next_position,
+
+	cal_obj_bymonth_filter,
+	NULL, /* BYWEEKNO is only applicable to YEARLY frequency. */
+	NULL, /* BYYEARDAY is not useful in a WEEKLY frequency. */
+	NULL, /* BYMONTHDAY is not useful in a WEEKLY frequency. */
+	cal_obj_byday_expand_weekly,
+	cal_obj_byhour_expand,
+	cal_obj_byminute_expand,
+	cal_obj_bysecond_expand
+};
+
+ECalRecurVTable cal_obj_daily_vtable = {
+	cal_obj_daily_find_start_position,
+	cal_obj_daily_find_next_position,
+
+	cal_obj_bymonth_filter,
+	NULL, /* BYWEEKNO is only applicable to YEARLY frequency. */
+	cal_obj_byyearday_filter,
+	cal_obj_bymonthday_filter,
+	cal_obj_byday_filter,
+	cal_obj_byhour_expand,
+	cal_obj_byminute_expand,
+	cal_obj_bysecond_expand
+};
+
+ECalRecurVTable cal_obj_hourly_vtable = {
+	cal_obj_hourly_find_start_position,
+	cal_obj_hourly_find_next_position,
+
+	cal_obj_bymonth_filter,
+	NULL, /* BYWEEKNO is only applicable to YEARLY frequency. */
+	cal_obj_byyearday_filter,
+	cal_obj_bymonthday_filter,
+	cal_obj_byday_filter,
+	cal_obj_byhour_filter,
+	cal_obj_byminute_expand,
+	cal_obj_bysecond_expand
+};
+
+ECalRecurVTable cal_obj_minutely_vtable = {
+	cal_obj_minutely_find_start_position,
+	cal_obj_minutely_find_next_position,
+
+	cal_obj_bymonth_filter,
+	NULL, /* BYWEEKNO is only applicable to YEARLY frequency. */
+	cal_obj_byyearday_filter,
+	cal_obj_bymonthday_filter,
+	cal_obj_byday_filter,
+	cal_obj_byhour_filter,
+	cal_obj_byminute_filter,
+	cal_obj_bysecond_expand
+};
+
+ECalRecurVTable cal_obj_secondly_vtable = {
+	cal_obj_secondly_find_start_position,
+	cal_obj_secondly_find_next_position,
+
+	cal_obj_bymonth_filter,
+	NULL, /* BYWEEKNO is only applicable to YEARLY frequency. */
+	cal_obj_byyearday_filter,
+	cal_obj_bymonthday_filter,
+	cal_obj_byday_filter,
+	cal_obj_byhour_filter,
+	cal_obj_byminute_filter,
+	cal_obj_bysecond_filter
+};
+
+/**
+ * e_cal_recur_generate_instances:
+ * @comp: A calendar component object.
+ * @start: Range start time.
+ * @end: Range end time.
+ * @cb: Callback function.
+ * @cb_data: Closure data for the callback function.
+ * @tz_cb: Callback for retrieving timezones.
+ * @tz_cb_data: Closure data for the timezone callback.
+ * @default_timezone: Default timezone to use when a timezone cannot be
+ * found.
+ *
+ * Calls the given callback function for each occurrence of the event that
+ * intersects the range between the given @start and @end times (the end time is
+ * not included). Note that the occurrences may start before the given start
+ * time.
+ *
+ * If the callback routine returns FALSE the occurrence generation stops.
+ *
+ * Both start and end can be -1, in which case we start at the events first
+ * instance and continue until it ends, or forever if it has no enddate.
+ *
+ * The tz_cb is used to resolve references to timezones. It is passed a TZID
+ * and should return the icaltimezone* corresponding to that TZID. We need to
+ * do this as we access timezones in different ways on the client & server.
+ *
+ * The default_timezone argument is used for DTSTART or DTEND properties that
+ * are DATE values or do not have a TZID (i.e. floating times).
+ */
+void
+e_cal_recur_generate_instances (ECalComponent		*comp,
+			      time_t			 start,
+			      time_t			 end,
+			      ECalRecurInstanceFn	 cb,
+			      gpointer                   cb_data,
+			      ECalRecurResolveTimezoneFn  tz_cb,
+			      gpointer			 tz_cb_data,
+			      icaltimezone		*default_timezone)
+{
+#if 0
+	g_print ("In e_cal_recur_generate_instances comp: %p\n", comp);
+	g_print ("  start: %li - %s", start, ctime (&start));
+	g_print ("  end  : %li - %s", end, ctime (&end));
+#endif
+	e_cal_recur_generate_instances_of_rule (comp, NULL, start, end,
+						cb, cb_data, tz_cb, tz_cb_data,
+						default_timezone);
+}
+
+
+/*
+ * Calls the given callback function for each occurrence of the given
+ * recurrence rule between the given start and end times. If the rule is NULL
+ * it uses all the rules from the component.
+ *
+ * If the callback routine returns FALSE the occurrence generation stops.
+ *
+ * The use of the specific rule is for determining the end of a rule when
+ * COUNT is set. The callback will count instances and store the enddate
+ * when COUNT is reached.
+ *
+ * Both start and end can be -1, in which case we start at the events first
+ * instance and continue until it ends, or forever if it has no enddate.
+ */
+static void
+e_cal_recur_generate_instances_of_rule (ECalComponent	 *comp,
+					icalproperty	 *prop,
+					time_t		  start,
+					time_t		  end,
+					ECalRecurInstanceFn  cb,
+					gpointer            cb_data,
+					ECalRecurResolveTimezoneFn  tz_cb,
+					gpointer	            tz_cb_data,
+					icaltimezone	 *default_timezone)
+{
+	ECalComponentDateTime dtstart, dtend;
+	time_t dtstart_time, dtend_time;
+	GSList *rrules = NULL, *rdates = NULL, elem;
+	GSList *exrules = NULL, *exdates = NULL;
+	CalObjTime interval_start, interval_end, event_start, event_end;
+	CalObjTime chunk_start, chunk_end;
+	gint days, seconds, year;
+	gboolean single_rule, convert_end_date = FALSE;
+	icaltimezone *start_zone = NULL, *end_zone = NULL;
+
+	g_return_if_fail (comp != NULL);
+	g_return_if_fail (cb != NULL);
+	g_return_if_fail (tz_cb != NULL);
+	g_return_if_fail (start >= -1);
+	g_return_if_fail (end >= -1);
+
+	/* Get dtstart, dtend, recurrences, and exceptions. Note that
+	   cal_component_get_dtend() will convert a DURATION property to a
+	   DTEND so we don't need to worry about that. */
+
+	e_cal_component_get_dtstart (comp, &dtstart);
+	e_cal_component_get_dtend (comp, &dtend);
+
+	if (!dtstart.value) {
+		g_message ("e_cal_recur_generate_instances_of_rule(): bogus "
+			   "component, does not have DTSTART.  Skipping...");
+		goto out;
+	}
+
+	/* For DATE-TIME values with a TZID, we use the supplied callback to
+	   resolve the TZID. For DATE values and DATE-TIME values without a
+	   TZID (i.e. floating times) we use the default timezone. */
+	if (dtstart.tzid && !dtstart.value->is_date) {
+		start_zone = (*tz_cb) (dtstart.tzid, tz_cb_data);
+		if (!start_zone)
+			start_zone = default_timezone;
+	} else {
+		start_zone = default_timezone;
+
+		/* Flag that we need to convert the saved ENDDATE property
+		   to the default timezone. */
+		convert_end_date = TRUE;
+	}
+
+	dtstart_time = icaltime_as_timet_with_zone (*dtstart.value,
+						    start_zone);
+	if (start == -1)
+		start = dtstart_time;
+
+	if (dtend.value) {
+		/* If both DTSTART and DTEND are DATE values, and they are the
+		   same day, we add 1 day to DTEND. This means that most
+		   events created with the old Evolution behavior will still
+		   work OK. I'm not sure what Outlook does in this case. */
+		if (dtstart.value->is_date && dtend.value->is_date) {
+			if (icaltime_compare_date_only (*dtstart.value,
+							*dtend.value) == 0) {
+				icaltime_adjust (dtend.value, 1, 0, 0, 0);
+			}
+		}
+	} else {
+		/* If there is no DTEND, then if DTSTART is a DATE-TIME value
+		   we use the same time (so we have a single point in time).
+		   If DTSTART is a DATE value we add 1 day. */
+		dtend.value = g_new (struct icaltimetype, 1);
+		*dtend.value = *dtstart.value;
+
+		if (dtstart.value->is_date) {
+			icaltime_adjust (dtend.value, 1, 0, 0, 0);
+		}
+	}
+
+	if (dtend.tzid && !dtend.value->is_date) {
+		end_zone = (*tz_cb) (dtend.tzid, tz_cb_data);
+		if (!end_zone)
+			end_zone = default_timezone;
+	} else {
+		end_zone = default_timezone;
+	}
+
+	/* We don't do this any more, since Outlook assumes that the DTEND
+	   date is not included. */
+#if 0
+	/* If DTEND is a DATE value, we add 1 day to it so that it includes
+	   the entire day. */
+	if (dtend.value->is_date) {
+		dtend.value->hour = 0;
+		dtend.value->minute = 0;
+		dtend.value->second = 0;
+		icaltime_adjust (dtend.value, 1, 0, 0, 0);
+	}
+#endif
+	dtend_time = icaltime_as_timet_with_zone (*dtend.value, end_zone);
+
+	/* If there is no recurrence, just call the callback if the event
+	   intersects the given interval. */
+	if (!(e_cal_component_has_recurrences (comp)
+	      || e_cal_component_has_exceptions (comp))) {
+		if ((end == -1 || dtstart_time < end) && dtend_time > start) {
+			(* cb) (comp, dtstart_time, dtend_time, cb_data);
+		}
+
+		goto out;
+	}
+
+	/* If a specific recurrence rule is being used, set up a simple list,
+	   else get the recurrence rules from the component. */
+	if (prop) {
+		single_rule = TRUE;
+
+		elem.data = prop;
+		elem.next = NULL;
+		rrules = &elem;
+	} else if (e_cal_component_is_instance (comp)) {
+		single_rule = FALSE;
+	} else {
+		single_rule = FALSE;
+
+		/* Make sure all the enddates for the rules are set. */
+		e_cal_recur_ensure_end_dates (comp, FALSE, tz_cb, tz_cb_data);
+
+		e_cal_component_get_rrule_property_list (comp, &rrules);
+		e_cal_component_get_rdate_list (comp, &rdates);
+		e_cal_component_get_exrule_property_list (comp, &exrules);
+		e_cal_component_get_exdate_list (comp, &exdates);
+	}
+
+	/* Convert the interval start & end to CalObjTime. Note that if end
+	   is -1 interval_end won't be set, so don't use it!
+	   Also note that we use end - 1 since we want the interval to be
+	   inclusive as it makes the code simpler. We do all calculation
+	   in the timezone of the DTSTART. */
+	cal_object_time_from_time (&interval_start, start, start_zone);
+	if (end != -1)
+		cal_object_time_from_time (&interval_end, end - 1, start_zone);
+
+	cal_object_time_from_time (&event_start, dtstart_time, start_zone);
+	cal_object_time_from_time (&event_end, dtend_time, start_zone);
+	
+	/* Calculate the duration of the event, which we use for all
+	   occurrences. We can't just subtract start from end since that may
+	   be affected by daylight-saving time. So we want a value of days
+	   + seconds. */
+	cal_object_compute_duration (&event_start, &event_end,
+				     &days, &seconds);
+
+	/* Take off the duration from interval_start, so we get occurrences
+	   that start just before the start time but overlap it. But only do
+	   that if the interval is after the event's start time. */
+	if (start > dtstart_time) {
+		cal_obj_time_add_days (&interval_start, -days);
+		cal_obj_time_add_seconds (&interval_start, -seconds);
+	}
+
+	/* Expand the recurrence for each year between start & end, or until
+	   the callback returns 0 if end is 0. We do a year at a time to
+	   give the callback function a chance to break out of the loop, and
+	   so we don't get into problems with infinite recurrences. Since we
+	   have to work on complete sets of occurrences, if there is a yearly
+	   frequency it wouldn't make sense to break it into smaller chunks,
+	   since we would then be calculating the same sets several times.
+	   Though this does mean that we sometimes do a lot more work than
+	   is necessary, e.g. if COUNT is set to something quite low. */
+	for (year = interval_start.year;
+	     (end == -1 || year <= interval_end.year) && year <= MAX_YEAR;
+	     year++) {
+		chunk_start = interval_start;
+		chunk_start.year = year;
+		if (end != -1)
+			chunk_end = interval_end;
+		chunk_end.year = year;
+
+		if (year != interval_start.year) {
+			chunk_start.month  = 0;
+			chunk_start.day    = 1;
+			chunk_start.hour   = 0;
+			chunk_start.minute = 0;
+			chunk_start.second = 0;
+		}
+		if (end == -1 || year != interval_end.year) {
+			chunk_end.month  = 11;
+			chunk_end.day    = 31;
+			chunk_end.hour   = 23;
+			chunk_end.minute = 59;
+			chunk_end.second = 61;
+			chunk_end.flags  = FALSE;
+		}
+
+		if (!generate_instances_for_chunk (comp, dtstart_time,
+						   start_zone,
+						   rrules, rdates,
+						   exrules, exdates,
+						   single_rule,
+						   &event_start,
+						   start,
+						   &chunk_start, &chunk_end,
+						   days, seconds,
+						   convert_end_date,
+						   cb, cb_data))
+			break;
+	}
+
+	if (!prop) {
+		e_cal_component_free_period_list (rdates);
+		e_cal_component_free_exdate_list (exdates);
+	}
+
+ out:
+	e_cal_component_free_datetime (&dtstart);
+	e_cal_component_free_datetime (&dtend);
+}
+
+/* Builds a list of GINT_TO_POINTER() elements out of a short array from a
+ * struct icalrecurrencetype.
+ */
+static GList *
+array_to_list (short *array, int max_elements)
+{
+	GList *l;
+	int i;
+
+	l = NULL;
+
+	for (i = 0; i < max_elements && array[i] != ICAL_RECURRENCE_ARRAY_MAX; i++)
+		l = g_list_prepend (l, GINT_TO_POINTER ((int) (array[i])));
+	return g_list_reverse (l);
+}
+
+/**
+ * e_cal_recur_from_icalproperty:
+ * @prop: An RRULE or EXRULE #icalproperty.
+ * @exception: TRUE if this is an EXRULE rather than an RRULE.
+ * @zone: The DTSTART timezone, used for converting the UNTIL property if it
+ * is given as a DATE value.
+ * @convert_end_date: TRUE if the saved end date needs to be converted to the
+ * given @zone timezone. This is needed if the DTSTART is a DATE or floating
+ * time.
+ * 
+ * Converts an #icalproperty to a #ECalRecurrence.  This should be
+ * freed using the e_cal_recur_free() function.
+ * 
+ * Return value: #ECalRecurrence structure.
+ **/
+static ECalRecurrence *
+e_cal_recur_from_icalproperty (icalproperty *prop, gboolean exception,
+			     icaltimezone *zone, gboolean convert_end_date)
+{
+	struct icalrecurrencetype ir;
+	ECalRecurrence *r;
+	gint max_elements, i;
+	GList *elem;
+
+	g_return_val_if_fail (prop != NULL, NULL);
+
+	r = g_new (ECalRecurrence, 1);
+
+	if (exception)
+		ir = icalproperty_get_exrule (prop);
+	else
+		ir = icalproperty_get_rrule (prop);
+
+	r->freq = ir.freq;
+	r->interval = ir.interval;
+
+	if (ir.count != 0) {
+		/* If COUNT is set, we use the pre-calculated enddate.
+		   Note that this can be 0 if the RULE doesn't actually
+		   generate COUNT instances. */
+		r->enddate = e_cal_recur_get_rule_end_date (prop, convert_end_date ? zone : NULL);
+	} else {
+		if (icaltime_is_null_time (ir.until)) {
+			/* If neither COUNT or UNTIL is set, the event
+			   recurs forever. */
+			r->enddate = 0;
+		} else if (ir.until.is_date) {
+			/* If UNTIL is a DATE, we stop at the end of
+			   the day, in local time (with the DTSTART timezone).
+			   Note that UNTIL is inclusive so we stop before
+			   midnight. */
+			ir.until.hour = 23;
+			ir.until.minute = 59;
+			ir.until.second = 59;
+			ir.until.is_date = FALSE;
+
+			r->enddate = icaltime_as_timet_with_zone (ir.until,
+								  zone);
+#if 0
+			g_print ("  until: %li - %s", r->enddate, ctime (&r->enddate));
+#endif
+
+		} else {
+			/* If UNTIL is a DATE-TIME, it must be in UTC. */
+			icaltimezone *utc_zone;
+			utc_zone = icaltimezone_get_utc_timezone ();
+			r->enddate = icaltime_as_timet_with_zone (ir.until,
+								  utc_zone);
+		}
+	}
+
+	r->week_start_day = e_cal_recur_ical_weekday_to_weekday (ir.week_start);
+
+	r->bymonth = array_to_list (ir.by_month,
+				    sizeof (ir.by_month) / sizeof (ir.by_month[0]));
+	for (elem = r->bymonth; elem; elem = elem->next) {
+		/* We need to convert from 1-12 to 0-11, i.e. subtract 1. */
+		int month = GPOINTER_TO_INT (elem->data) - 1;
+		elem->data = GINT_TO_POINTER (month);
+	}
+
+	r->byweekno = array_to_list (ir.by_week_no,
+				     sizeof (ir.by_week_no) / sizeof (ir.by_week_no[0]));
+
+	r->byyearday = array_to_list (ir.by_year_day,
+				      sizeof (ir.by_year_day) / sizeof (ir.by_year_day[0]));
+
+	r->bymonthday = array_to_list (ir.by_month_day,
+				       sizeof (ir.by_month_day) / sizeof (ir.by_month_day[0]));
+
+	/* FIXME: libical only supports 8 values, out of possible 107 * 7. */
+	r->byday = NULL;
+	max_elements = sizeof (ir.by_day) / sizeof (ir.by_day[0]);
+	for (i = 0; i < max_elements && ir.by_day[i] != ICAL_RECURRENCE_ARRAY_MAX; i++) {
+		enum icalrecurrencetype_weekday day;
+		gint weeknum, weekday;
+
+		day = icalrecurrencetype_day_day_of_week (ir.by_day[i]);
+		weeknum = icalrecurrencetype_day_position (ir.by_day[i]);
+
+		weekday = e_cal_recur_ical_weekday_to_weekday (day);
+
+		r->byday = g_list_prepend (r->byday,
+					   GINT_TO_POINTER (weeknum));
+		r->byday = g_list_prepend (r->byday,
+					   GINT_TO_POINTER (weekday));
+	}
+
+	r->byhour = array_to_list (ir.by_hour,
+				   sizeof (ir.by_hour) / sizeof (ir.by_hour[0]));
+
+	r->byminute = array_to_list (ir.by_minute,
+				     sizeof (ir.by_minute) / sizeof (ir.by_minute[0]));
+
+	r->bysecond = array_to_list (ir.by_second,
+				     sizeof (ir.by_second) / sizeof (ir.by_second[0]));
+
+	r->bysetpos = array_to_list (ir.by_set_pos,
+				     sizeof (ir.by_set_pos) / sizeof (ir.by_set_pos[0]));
+
+	return r;
+}
+
+
+static gint
+e_cal_recur_ical_weekday_to_weekday	(enum icalrecurrencetype_weekday day)
+{
+	gint weekday;
+
+	switch (day) {
+	case ICAL_NO_WEEKDAY:		/* Monday is the default in RFC2445. */
+	case ICAL_MONDAY_WEEKDAY:
+		weekday = 0;
+		break;
+	case ICAL_TUESDAY_WEEKDAY:
+		weekday = 1;
+		break;
+	case ICAL_WEDNESDAY_WEEKDAY:
+		weekday = 2;
+		break;
+	case ICAL_THURSDAY_WEEKDAY:
+		weekday = 3;
+		break;
+	case ICAL_FRIDAY_WEEKDAY:
+		weekday = 4;
+		break;
+	case ICAL_SATURDAY_WEEKDAY:
+		weekday = 5;
+		break;
+	case ICAL_SUNDAY_WEEKDAY:
+		weekday = 6;
+		break;
+	default:
+		g_warning ("e_cal_recur_ical_weekday_to_weekday(): Unknown week day %d",
+			   day);
+		weekday = 0;
+	}
+
+	return weekday;
+}
+
+
+/**
+ * e_cal_recur_free:
+ * @r: A #ECalRecurrence structure.
+ * 
+ * Frees a #ECalRecurrence structure.
+ **/
+static void
+e_cal_recur_free (ECalRecurrence *r)
+{
+	g_return_if_fail (r != NULL);
+
+	g_list_free (r->bymonth);
+	g_list_free (r->byweekno);
+	g_list_free (r->byyearday);
+	g_list_free (r->bymonthday);
+	g_list_free (r->byday);
+	g_list_free (r->byhour);
+	g_list_free (r->byminute);
+	g_list_free (r->bysecond);
+	g_list_free (r->bysetpos);
+
+	g_free (r);
+}
+
+/* Generates one year's worth of recurrence instances.  Returns TRUE if all the
+ * callback invocations returned TRUE, or FALSE when any one of them returns
+ * FALSE, i.e. meaning that the instance generation should be stopped.
+ *
+ * This should only output instances whose start time is between chunk_start
+ * and chunk_end (inclusive), or we may generate duplicates when we do the next
+ * chunk. (This applies mainly to weekly recurrences, since weeks can span 2
+ * years.)
+ *
+ * It should also only output instances that are on or after the event's
+ * DTSTART property and that intersect the required interval, between
+ * interval_start and interval_end.
+ */
+static gboolean
+generate_instances_for_chunk (ECalComponent	*comp,
+			      time_t             comp_dtstart,
+			      icaltimezone	*zone,
+			      GSList		*rrules,
+			      GSList		*rdates,
+			      GSList		*exrules,
+			      GSList		*exdates,
+			      gboolean		 single_rule,
+			      CalObjTime	*event_start,
+			      time_t		 interval_start,
+			      CalObjTime	*chunk_start,
+			      CalObjTime	*chunk_end,
+			      gint		 duration_days,
+			      gint		 duration_seconds,
+			      gboolean		 convert_end_date,
+			      ECalRecurInstanceFn cb,
+			      gpointer           cb_data)
+{
+	GArray *occs, *ex_occs, *tmp_occs, *rdate_periods;
+	CalObjTime cotime, *occ;
+	GSList *elem;
+	gint i;
+	time_t start_time, end_time;
+	struct icaltimetype start_tt, end_tt;
+	gboolean cb_status = TRUE, rule_finished, finished = TRUE;
+
+#if 0
+	g_print ("In generate_instances_for_chunk rrules: %p\n"
+		 "  %i/%i/%i %02i:%02i:%02i - %i/%i/%i %02i:%02i:%02i\n",
+		 rrules,
+		 chunk_start->day, chunk_start->month + 1,
+		 chunk_start->year, chunk_start->hour,
+		 chunk_start->minute, chunk_start->second,
+		 chunk_end->day, chunk_end->month + 1,
+		 chunk_end->year, chunk_end->hour,
+		 chunk_end->minute, chunk_end->second);
+#endif
+
+	occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
+	ex_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
+	rdate_periods = g_array_new (FALSE, FALSE,
+				     sizeof (CalObjRecurrenceDate));
+
+	/* The original DTSTART property is included in the occurrence set,
+	   but not if we are just generating occurrences for a single rule. */
+	if (!single_rule) {
+		/* We add it if it is in this chunk. If it is after this chunk
+		   we set finished to FALSE, since we know we aren't finished
+		   yet. */
+		if (cal_obj_time_compare_func (event_start, chunk_end) >= 0)
+			finished = FALSE;
+		else if (cal_obj_time_compare_func (event_start, chunk_start) >= 0)
+			g_array_append_vals (occs, event_start, 1);
+	}
+	
+	/* Expand each of the recurrence rules. */
+	for (elem = rrules; elem; elem = elem->next) {
+		icalproperty *prop;
+		ECalRecurrence *r;
+
+		prop = elem->data;
+		r = e_cal_recur_from_icalproperty (prop, FALSE, zone,
+						 convert_end_date);
+
+		tmp_occs = cal_obj_expand_recurrence (event_start, zone, r,
+						      chunk_start,
+						      chunk_end,
+						      &rule_finished);
+		e_cal_recur_free (r);
+
+		/* If any of the rules return FALSE for finished, we know we
+		   have to carry on so we set finished to FALSE. */
+		if (!rule_finished)
+			finished = FALSE;
+
+		g_array_append_vals (occs, tmp_occs->data, tmp_occs->len);
+		g_array_free (tmp_occs, TRUE);
+	}
+
+	/* Add on specific RDATE occurrence dates. If they have an end time
+	   or duration set, flag them as RDATEs, and store a pointer to the
+	   period in the rdate_periods array. Otherwise we can just treat them
+	   as normal occurrences. */
+	for (elem = rdates; elem; elem = elem->next) {
+		ECalComponentPeriod *p;
+		CalObjRecurrenceDate rdate;
+
+		p = elem->data;
+
+		/* FIXME: We currently assume RDATEs are in the same timezone
+		   as DTSTART. We should get the RDATE timezone and convert
+		   to the DTSTART timezone first. */
+		cotime.year     = p->start.year;
+		cotime.month    = p->start.month - 1;
+		cotime.day      = p->start.day;
+		cotime.hour     = p->start.hour;
+		cotime.minute   = p->start.minute;
+		cotime.second   = p->start.second;
+		cotime.flags    = FALSE;
+
+		/* If the rdate is after the current chunk we set finished
+		   to FALSE, and we skip it. */
+		if (cal_obj_time_compare_func (&cotime, chunk_end) >= 0) {
+			finished = FALSE;
+			continue;
+		}
+
+		/* Check if the end date or duration is set. If it is we need
+		   to store it so we can get it later. (libical seems to set
+		   second to -1 to denote an unset time. See icalvalue.c)
+		   FIXME. */
+		if (p->type != E_CAL_COMPONENT_PERIOD_DATETIME
+		    || p->u.end.second != -1) {
+			cotime.flags = TRUE;
+
+			rdate.start = cotime;
+			rdate.period = p;
+			g_array_append_val (rdate_periods, rdate);
+		}
+
+		g_array_append_val (occs, cotime);
+	}
+
+	/* Expand each of the exception rules. */
+	for (elem = exrules; elem; elem = elem->next) {
+		icalproperty *prop;
+		ECalRecurrence *r;
+
+		prop = elem->data;
+		r = e_cal_recur_from_icalproperty (prop, FALSE, zone,
+						 convert_end_date);
+
+		tmp_occs = cal_obj_expand_recurrence (event_start, zone, r,
+						      chunk_start,
+						      chunk_end,
+						      &rule_finished);
+		e_cal_recur_free (r);
+
+		g_array_append_vals (ex_occs, tmp_occs->data, tmp_occs->len);
+		g_array_free (tmp_occs, TRUE);
+	}
+
+	/* Add on specific exception dates. */
+	for (elem = exdates; elem; elem = elem->next) {
+		ECalComponentDateTime *cdt;
+
+		cdt = elem->data;
+
+		/* FIXME: We currently assume EXDATEs are in the same timezone
+		   as DTSTART. We should get the EXDATE timezone and convert
+		   to the DTSTART timezone first. */
+		cotime.year     = cdt->value->year;
+		cotime.month    = cdt->value->month - 1;
+		cotime.day      = cdt->value->day;
+
+		/* If the EXDATE has a DATE value, set the time to the start
+		   of the day and set flags to TRUE so we know to skip all
+		   occurrences on that date. */
+		if (cdt->value->is_date) {
+			cotime.hour     = 0;
+			cotime.minute   = 0;
+			cotime.second   = 0;
+			cotime.flags    = TRUE;
+		} else {
+			cotime.hour     = cdt->value->hour;
+			cotime.minute   = cdt->value->minute;
+			cotime.second   = cdt->value->second;
+			cotime.flags    = FALSE;
+		}
+
+		g_array_append_val (ex_occs, cotime);
+	}
+
+
+	/* Sort all the arrays. */
+	cal_obj_sort_occurrences (occs);
+	cal_obj_sort_occurrences (ex_occs);
+
+	qsort (rdate_periods->data, rdate_periods->len,
+	       sizeof (CalObjRecurrenceDate), cal_obj_time_compare_func);
+
+	/* Create the final array, by removing the exceptions from the
+	   occurrences, and removing any duplicates. */
+	cal_obj_remove_exceptions (occs, ex_occs);
+
+	/* Call the callback for each occurrence. If it returns 0 we break
+	   out of the loop. */
+	for (i = 0; i < occs->len; i++) {
+		/* Convert each CalObjTime into a start & end time_t, and
+		   check it is within the bounds of the event & interval. */
+		occ = &g_array_index (occs, CalObjTime, i);
+#if 0
+		g_print ("Checking occurrence: %s\n",
+			 cal_obj_time_to_string (occ));
+#endif
+		start_tt = icaltime_null_time ();
+		start_tt.year   = occ->year;
+		start_tt.month  = occ->month + 1;
+		start_tt.day    = occ->day;
+		start_tt.hour   = occ->hour;
+		start_tt.minute = occ->minute;
+		start_tt.second = occ->second;
+		start_time = icaltime_as_timet_with_zone (start_tt, zone);
+
+		if (start_time == -1) {
+			g_warning ("time_t out of range");
+			finished = TRUE;
+			break;
+		}
+
+		/* Check to ensure that the start time is at or after the
+		   event's DTSTART time, and that it is inside the chunk that
+		   we are currently working on. (Note that the chunk_end time
+		   is never after the interval end time, so this also tests
+		   that we don't go past the end of the required interval). */
+		if (start_time < comp_dtstart
+		    || cal_obj_time_compare_func (occ, chunk_start) < 0
+		    || cal_obj_time_compare_func (occ, chunk_end) > 0) {
+#if 0
+			g_print ("  start time invalid\n");
+#endif
+			continue;
+		}
+
+		if (occ->flags) {
+			/* If it is an RDATE, we see if the end date or
+			   duration was set. If not, we use the same duration
+			   as the original occurrence. */
+			if (!cal_object_get_rdate_end (occ, rdate_periods)) {
+				cal_obj_time_add_days (occ, duration_days);
+				cal_obj_time_add_seconds (occ,
+							  duration_seconds);
+			}
+		} else {
+			cal_obj_time_add_days (occ, duration_days);
+			cal_obj_time_add_seconds (occ, duration_seconds);
+		}
+
+		end_tt = icaltime_null_time ();
+		end_tt.year   = occ->year;
+		end_tt.month  = occ->month + 1;
+		end_tt.day    = occ->day;
+		end_tt.hour   = occ->hour;
+		end_tt.minute = occ->minute;
+		end_tt.second = occ->second;
+		end_time = icaltime_as_timet_with_zone (end_tt, zone);
+
+		if (end_time == -1) {
+			g_warning ("time_t out of range");
+			finished = TRUE;
+			break;
+		}
+
+		/* Check that the end time is after the interval start, so we
+		   know that it intersects the required interval. */
+		if (end_time <= interval_start) {
+#if 0
+			g_print ("  end time invalid\n");
+#endif
+			continue;
+		}
+
+		cb_status = (*cb) (comp, start_time, end_time, cb_data);
+		if (!cb_status)
+			break;
+	}
+
+	g_array_free (occs, TRUE);
+	g_array_free (ex_occs, TRUE);
+	g_array_free (rdate_periods, TRUE);
+
+	/* We return TRUE (i.e. carry on) only if the callback has always
+	   returned TRUE and we know that we have more occurrences to generate
+	   (i.e. finished is FALSE). */
+	return cb_status && !finished;
+}
+
+
+/* This looks up the occurrence time in the sorted rdate_periods array, and
+   tries to compute the end time of the occurrence. If no end time or duration
+   is set it returns FALSE and the default duration will be used. */
+static gboolean
+cal_object_get_rdate_end	(CalObjTime	*occ,
+				 GArray		*rdate_periods)
+{
+	CalObjRecurrenceDate *rdate = NULL;
+	ECalComponentPeriod *p;
+	gint lower, upper, middle, cmp = 0;
+
+	lower = 0;
+	upper = rdate_periods->len;
+
+	while (lower < upper) {
+		middle = (lower + upper) >> 1;
+	  
+		rdate = &g_array_index (rdate_periods, CalObjRecurrenceDate,
+					middle);
+
+		cmp = cal_obj_time_compare_func (occ, &rdate->start);
+	  
+		if (cmp == 0)
+			break;
+		else if (cmp < 0)
+			upper = middle;
+		else
+			lower = middle + 1;
+	}
+
+	/* This should never happen. */
+	if (cmp == 0) {
+		g_warning ("Recurrence date not found");
+		return FALSE;
+	}
+
+	p = rdate->period;
+	if (p->type == E_CAL_COMPONENT_PERIOD_DATETIME) {
+		/* FIXME: We currently assume RDATEs are in the same timezone
+		   as DTSTART. We should get the RDATE timezone and convert
+		   to the DTSTART timezone first. */
+		occ->year     = p->u.end.year;
+		occ->month    = p->u.end.month - 1;
+		occ->day      = p->u.end.day;
+		occ->hour     = p->u.end.hour;
+		occ->minute   = p->u.end.minute;
+		occ->second   = p->u.end.second;
+		occ->flags    = FALSE;
+	} else {
+		cal_obj_time_add_days (occ, p->u.duration.weeks * 7
+				       + p->u.duration.days);
+		cal_obj_time_add_hours (occ, p->u.duration.hours);
+		cal_obj_time_add_minutes (occ, p->u.duration.minutes);
+		cal_obj_time_add_seconds (occ, p->u.duration.seconds);
+	}
+
+	return TRUE;
+}
+
+
+static void
+cal_object_compute_duration (CalObjTime *start,
+			     CalObjTime *end,
+			     gint	*days,
+			     gint	*seconds)
+{
+	GDate start_date, end_date;
+	gint start_seconds, end_seconds;
+
+	g_date_clear (&start_date, 1);
+	g_date_clear (&end_date, 1);
+	g_date_set_dmy (&start_date, start->day, start->month + 1,
+			start->year);
+	g_date_set_dmy (&end_date, end->day, end->month + 1,
+			end->year);
+
+	*days = g_date_get_julian (&end_date) - g_date_get_julian (&start_date);
+	start_seconds = start->hour * 3600 + start->minute * 60
+		+ start->second;
+	end_seconds = end->hour * 3600 + end->minute * 60 + end->second;
+
+	*seconds = end_seconds - start_seconds;
+	if (*seconds < 0) {
+		*days = *days - 1;
+		*seconds += 24 * 60 * 60;
+	}
+}
+
+
+/* Returns an unsorted GArray of CalObjTime's resulting from expanding the
+   given recurrence rule within the given interval. Note that it doesn't
+   clip the generated occurrences to the interval, i.e. if the interval
+   starts part way through the year this function still returns all the
+   occurrences for the year. Clipping is done later.
+   The finished flag is set to FALSE if there are more occurrences to generate
+   after the given interval.*/
+static GArray*
+cal_obj_expand_recurrence		(CalObjTime	  *event_start,
+					 icaltimezone	  *zone,
+					 ECalRecurrence	  *recur,
+					 CalObjTime	  *interval_start,
+					 CalObjTime	  *interval_end,
+					 gboolean	  *finished)
+{
+	ECalRecurVTable *vtable;
+	CalObjTime *event_end = NULL, event_end_cotime;
+	RecurData recur_data;
+	CalObjTime occ, *cotime;
+	GArray *all_occs, *occs;
+	gint len;
+
+	/* This is the resulting array of CalObjTime elements. */
+	all_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
+
+	*finished = TRUE;
+
+	vtable = cal_obj_get_vtable (recur->freq);
+	if (!vtable)
+		return all_occs;
+
+	/* Calculate some useful data such as some fast lookup tables. */
+	cal_obj_initialize_recur_data (&recur_data, recur, event_start);
+
+	/* Compute the event_end, if the recur's enddate is set. */
+	if (recur->enddate > 0) {
+		cal_object_time_from_time (&event_end_cotime,
+					   recur->enddate, zone);
+		event_end = &event_end_cotime;
+
+		/* If the enddate is before the requested interval return. */
+		if (cal_obj_time_compare_func (event_end, interval_start) < 0)
+			return all_occs;
+	}
+
+	/* Set finished to FALSE if we know there will be more occurrences to
+	   do after this interval. */
+	if (!interval_end || !event_end
+	    || cal_obj_time_compare_func (event_end, interval_end) > 0)
+		*finished = FALSE;
+
+	/* Get the first period based on the frequency and the interval that
+	   intersects the interval between start and end. */
+	if ((*vtable->find_start_position) (event_start, event_end,
+					    &recur_data,
+					    interval_start, interval_end,
+					    &occ))
+		return all_occs;
+
+	/* Loop until the event ends or we go past the end of the required
+	   interval. */
+	for (;;) {
+		/* Generate the set of occurrences for this period. */
+		switch (recur->freq) {
+		case ICAL_YEARLY_RECURRENCE:
+			occs = cal_obj_generate_set_yearly (&recur_data,
+							    vtable, &occ);
+			break;
+		case ICAL_MONTHLY_RECURRENCE:
+			occs = cal_obj_generate_set_monthly (&recur_data,
+							     vtable, &occ);
+			break;
+		default:
+			occs = cal_obj_generate_set_default (&recur_data,
+							     vtable, &occ);
+			break;
+		}
+
+		/* Sort the occurrences and remove duplicates. */
+		cal_obj_sort_occurrences (occs);
+		cal_obj_remove_duplicates_and_invalid_dates (occs);
+
+		/* Apply the BYSETPOS property. */
+		occs = cal_obj_bysetpos_filter (recur, occs);
+
+		/* Remove any occs after event_end. */
+		len = occs->len - 1;
+		if (event_end) {
+			while (len >= 0) {
+				cotime = &g_array_index (occs, CalObjTime,
+							 len);
+				if (cal_obj_time_compare_func (cotime,
+							       event_end) <= 0)
+					break;
+				len--;
+			}
+		}
+
+		/* Add the occurrences onto the main array. */
+		if (len >= 0)
+			g_array_append_vals (all_occs, occs->data, len + 1);
+
+ 		g_array_free (occs, TRUE);
+
+		/* Skip to the next period, or exit the loop if finished. */
+		if ((*vtable->find_next_position) (&occ, event_end,
+						   &recur_data, interval_end))
+			break;
+	}
+
+	return all_occs;
+}
+
+
+static GArray*
+cal_obj_generate_set_yearly	(RecurData *recur_data,
+				 ECalRecurVTable *vtable,
+				 CalObjTime *occ)
+{
+	ECalRecurrence *recur = recur_data->recur;
+	GArray *occs_arrays[4], *occs, *occs2;
+	gint num_occs_arrays = 0, i;
+
+	/* This is a bit complicated, since the iCalendar spec says that
+	   several BYxxx modifiers can be used simultaneously. So we have to
+	   be quite careful when determining the days of the occurrences.
+	   The BYHOUR, BYMINUTE & BYSECOND modifiers are no problem at all.
+
+	   The modifiers we have to worry about are: BYMONTH, BYWEEKNO,
+	   BYYEARDAY, BYMONTHDAY & BYDAY. We can't do these sequentially
+	   since each filter will mess up the results of the previous one.
+	   But they aren't all completely independant, e.g. BYMONTHDAY and
+	   BYDAY are related to BYMONTH, and BYDAY is related to BYWEEKNO.
+
+	   BYDAY & BYMONTHDAY can also be applied independently, which makes
+	   it worse. So we assume that if BYMONTH or BYWEEKNO is used, then
+	   the BYDAY modifier applies to those, else it is applied
+	   independantly.
+
+	   We expand the occurrences in parallel into the occs_arrays[] array,
+	   and then merge them all into one GArray before expanding BYHOUR,
+	   BYMINUTE & BYSECOND. */
+
+	if (recur->bymonth) {
+		occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
+		g_array_append_vals (occs, occ, 1);
+
+		occs = (*vtable->bymonth_filter) (recur_data, occs);
+
+		/* If BYMONTHDAY & BYDAY are both set we need to expand them
+		   in parallel and add the results. */
+		if (recur->bymonthday && recur->byday) {
+			/* Copy the occs array. */
+			occs2 = g_array_new (FALSE, FALSE,
+					     sizeof (CalObjTime));
+			g_array_append_vals (occs2, occs->data, occs->len);
+
+			occs = (*vtable->bymonthday_filter) (recur_data, occs);
+			/* Note that we explicitly call the monthly version
+			   of the BYDAY expansion filter. */
+			occs2 = cal_obj_byday_expand_monthly (recur_data,
+							      occs2);
+
+			/* Add the 2 resulting arrays together. */
+			g_array_append_vals (occs, occs2->data, occs2->len);
+			g_array_free (occs2, TRUE);
+		} else {
+			occs = (*vtable->bymonthday_filter) (recur_data, occs);
+			/* Note that we explicitly call the monthly version
+			   of the BYDAY expansion filter. */
+			occs = cal_obj_byday_expand_monthly (recur_data, occs);
+		}
+
+		occs_arrays[num_occs_arrays++] = occs;
+	}
+
+	if (recur->byweekno) {
+		occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
+		g_array_append_vals (occs, occ, 1);
+
+		occs = (*vtable->byweekno_filter) (recur_data, occs);
+		/* Note that we explicitly call the weekly version of the
+		   BYDAY expansion filter. */
+		occs = cal_obj_byday_expand_weekly (recur_data, occs);
+
+		occs_arrays[num_occs_arrays++] = occs;
+	}
+
+	if (recur->byyearday) {
+		occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
+		g_array_append_vals (occs, occ, 1);
+
+		occs = (*vtable->byyearday_filter) (recur_data, occs);
+
+		occs_arrays[num_occs_arrays++] = occs;
+	}
+
+	/* If BYMONTHDAY is set, and BYMONTH is not set, we need to
+	   expand BYMONTHDAY independantly. */
+	if (recur->bymonthday && !recur->bymonth) {
+		occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
+		g_array_append_vals (occs, occ, 1);
+
+		occs = (*vtable->bymonthday_filter) (recur_data, occs);
+
+		occs_arrays[num_occs_arrays++] = occs;
+	}
+
+	/* If BYDAY is set, and BYMONTH and BYWEEKNO are not set, we need to
+	   expand BYDAY independantly. */
+	if (recur->byday && !recur->bymonth && !recur->byweekno) {
+		occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
+		g_array_append_vals (occs, occ, 1);
+
+		occs = (*vtable->byday_filter) (recur_data, occs);
+
+		occs_arrays[num_occs_arrays++] = occs;
+	}
+
+	/* Add all the arrays together. If no filters were used we just
+	   create an array with one element. */
+	if (num_occs_arrays > 0) {
+		occs = occs_arrays[0];
+		for (i = 1; i < num_occs_arrays; i++) {
+			occs2 = occs_arrays[i];
+			g_array_append_vals (occs, occs2->data, occs2->len);
+			g_array_free (occs2, TRUE);
+		}
+	} else {
+		occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
+		g_array_append_vals (occs, occ, 1);
+	}
+
+	/* Now expand BYHOUR, BYMINUTE & BYSECOND. */
+	occs = (*vtable->byhour_filter) (recur_data, occs);
+	occs = (*vtable->byminute_filter) (recur_data, occs);
+	occs = (*vtable->bysecond_filter) (recur_data, occs);
+
+	return occs;
+}
+
+
+static GArray*
+cal_obj_generate_set_monthly	(RecurData *recur_data,
+				 ECalRecurVTable *vtable,
+				 CalObjTime *occ)
+{
+	GArray *occs, *occs2;
+
+	/* We start with just the one time in each set. */
+	occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
+	g_array_append_vals (occs, occ, 1);
+
+	occs = (*vtable->bymonth_filter) (recur_data, occs);
+
+	/* We need to combine the output of BYMONTHDAY & BYDAY, by doing them
+	   in parallel rather than sequentially. If we did them sequentially
+	   then we would lose the occurrences generated by BYMONTHDAY, and
+	   instead have repetitions of the occurrences from BYDAY. */
+	if (recur_data->recur->bymonthday && recur_data->recur->byday) {
+		occs2 = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
+		g_array_append_vals (occs2, occs->data, occs->len);
+
+		occs = (*vtable->bymonthday_filter) (recur_data, occs);
+		occs2 = (*vtable->byday_filter) (recur_data, occs2);
+
+		g_array_append_vals (occs, occs2->data, occs2->len);
+		g_array_free (occs2, TRUE);
+	} else {
+		occs = (*vtable->bymonthday_filter) (recur_data, occs);
+		occs = (*vtable->byday_filter) (recur_data, occs);
+	}
+
+	occs = (*vtable->byhour_filter) (recur_data, occs);
+	occs = (*vtable->byminute_filter) (recur_data, occs);
+	occs = (*vtable->bysecond_filter) (recur_data, occs);
+
+	return occs;
+}
+
+
+static GArray*
+cal_obj_generate_set_default	(RecurData *recur_data,
+				 ECalRecurVTable *vtable,
+				 CalObjTime *occ)
+{
+	GArray *occs;
+
+#if 0
+	g_print ("Generating set for %i/%i/%i %02i:%02i:%02i\n",
+		 occ->day, occ->month + 1, occ->year, occ->hour, occ->minute,
+		 occ->second);
+#endif
+
+	/* We start with just the one time in the set. */
+	occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
+	g_array_append_vals (occs, occ, 1);
+
+	occs = (*vtable->bymonth_filter) (recur_data, occs);
+	if (vtable->byweekno_filter)
+		occs = (*vtable->byweekno_filter) (recur_data, occs);
+	if (vtable->byyearday_filter)
+		occs = (*vtable->byyearday_filter) (recur_data, occs);
+	if (vtable->bymonthday_filter)
+		occs = (*vtable->bymonthday_filter) (recur_data, occs);
+	occs = (*vtable->byday_filter) (recur_data, occs);
+
+	occs = (*vtable->byhour_filter) (recur_data, occs);
+	occs = (*vtable->byminute_filter) (recur_data, occs);
+	occs = (*vtable->bysecond_filter) (recur_data, occs);
+
+	return occs;
+}
+
+
+
+/* Returns the function table corresponding to the recurrence frequency. */
+static ECalRecurVTable* cal_obj_get_vtable (icalrecurrencetype_frequency recur_type)
+{
+	ECalRecurVTable* vtable;
+
+	switch (recur_type) {
+	case ICAL_YEARLY_RECURRENCE:
+		vtable = &cal_obj_yearly_vtable;
+		break;
+	case ICAL_MONTHLY_RECURRENCE:
+		vtable = &cal_obj_monthly_vtable;
+		break;
+	case ICAL_WEEKLY_RECURRENCE:
+		vtable = &cal_obj_weekly_vtable;
+		break;
+	case ICAL_DAILY_RECURRENCE:
+		vtable = &cal_obj_daily_vtable;
+		break;
+	case ICAL_HOURLY_RECURRENCE:
+		vtable = &cal_obj_hourly_vtable;
+		break;
+	case ICAL_MINUTELY_RECURRENCE:
+		vtable = &cal_obj_minutely_vtable;
+		break;
+	case ICAL_SECONDLY_RECURRENCE:
+		vtable = &cal_obj_secondly_vtable;
+		break;
+	default:
+		g_warning ("Unknown recurrence frequency");
+		vtable = NULL;
+	}
+
+	return vtable;
+}
+
+
+/* This creates a number of fast lookup tables used when filtering with the
+   modifier properties BYMONTH, BYYEARDAY etc. */
+static void
+cal_obj_initialize_recur_data (RecurData  *recur_data,
+			       ECalRecurrence *recur,
+			       CalObjTime *event_start)
+{
+	GList *elem;
+	gint month, yearday, monthday, weekday, week_num, hour, minute, second;
+
+	/* Clear the entire RecurData. */
+	memset (recur_data, 0, sizeof (RecurData));
+
+	recur_data->recur = recur;
+
+	/* Set the weekday, used for the WEEKLY frequency and the BYWEEKNO
+	   modifier. */
+	recur_data->weekday_offset = cal_obj_time_weekday_offset (event_start,
+								  recur);
+
+	/* Create an array of months from bymonths for fast lookup. */
+	elem = recur->bymonth;
+	while (elem) {
+		month = GPOINTER_TO_INT (elem->data);
+		recur_data->months[month] = 1;
+		elem = elem->next;
+	}
+
+	/* Create an array of yeardays from byyearday for fast lookup.
+	   We create a second array to handle the negative values. The first
+	   element there corresponds to the last day of the year. */
+	elem = recur->byyearday;
+	while (elem) {
+		yearday = GPOINTER_TO_INT (elem->data);
+		if (yearday >= 0)
+			recur_data->yeardays[yearday] = 1;
+		else
+			recur_data->neg_yeardays[-yearday] = 1;
+		elem = elem->next;
+	}
+
+	/* Create an array of monthdays from bymonthday for fast lookup.
+	   We create a second array to handle the negative values. The first
+	   element there corresponds to the last day of the month. */
+	elem = recur->bymonthday;
+	while (elem) {
+		monthday = GPOINTER_TO_INT (elem->data);
+		if (monthday >= 0)
+			recur_data->monthdays[monthday] = 1;
+		else
+			recur_data->neg_monthdays[-monthday] = 1;
+		elem = elem->next;
+	}
+
+	/* Create an array of weekdays from byday for fast lookup. */
+	elem = recur->byday;
+	while (elem) {
+		weekday = GPOINTER_TO_INT (elem->data);
+		elem = elem->next;
+		/* The week number is not used when filtering. */
+		week_num = GPOINTER_TO_INT (elem->data);
+		elem = elem->next;
+
+		recur_data->weekdays[weekday] = 1;
+	}
+
+	/* Create an array of hours from byhour for fast lookup. */
+	elem = recur->byhour;
+	while (elem) {
+		hour = GPOINTER_TO_INT (elem->data);
+		recur_data->hours[hour] = 1;
+		elem = elem->next;
+	}
+
+	/* Create an array of minutes from byminutes for fast lookup. */
+	elem = recur->byminute;
+	while (elem) {
+		minute = GPOINTER_TO_INT (elem->data);
+		recur_data->minutes[minute] = 1;
+		elem = elem->next;
+	}
+
+	/* Create an array of seconds from byseconds for fast lookup. */
+	elem = recur->bysecond;
+	while (elem) {
+		second = GPOINTER_TO_INT (elem->data);
+		recur_data->seconds[second] = 1;
+		elem = elem->next;
+	}
+}
+
+
+static void
+cal_obj_sort_occurrences (GArray *occs)
+{
+	qsort (occs->data, occs->len, sizeof (CalObjTime),
+	       cal_obj_time_compare_func);
+}
+
+
+static void
+cal_obj_remove_duplicates_and_invalid_dates (GArray *occs)
+{
+	static const int days_in_month[12] = {
+		31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+	};
+
+	CalObjTime *occ, *prev_occ = NULL;
+	gint len, i, j = 0, year, month, days;
+	gboolean keep_occ;
+
+	len = occs->len;
+	for (i = 0; i < len; i++) {
+		occ = &g_array_index (occs, CalObjTime, i);
+		keep_occ = TRUE;
+
+		if (prev_occ && cal_obj_time_compare_func (occ,
+							   prev_occ) == 0)
+			keep_occ = FALSE;
+
+		year = occ->year;
+		month = occ->month;
+		days = days_in_month[occ->month];
+		/* If it is february and a leap year, add a day. */
+		if (month == 1 && (year % 4 == 0
+				   && (year % 100 != 0
+				       || year % 400 == 0)))
+			days++;
+
+		if (occ->day > days) {
+			/* move occurrence to the last day of the month */
+			occ->day = days;
+		}
+
+		if (keep_occ) {
+			if (i != j)
+				g_array_index (occs, CalObjTime, j)
+					= g_array_index (occs, CalObjTime, i);
+			j++;
+		}
+
+		prev_occ = occ;
+	}
+
+	g_array_set_size (occs, j);
+}
+
+
+/* Removes the exceptions from the ex_occs array from the occurrences in the
+   occs array, and removes any duplicates. Both arrays are sorted. */
+static void
+cal_obj_remove_exceptions (GArray *occs,
+			   GArray *ex_occs)
+{
+	CalObjTime *occ, *prev_occ = NULL, *ex_occ = NULL, *last_occ_kept;
+	gint i, j = 0, cmp, ex_index, occs_len, ex_occs_len;
+	gboolean keep_occ, current_time_is_exception = FALSE;
+
+	if (occs->len == 0)
+		return;
+
+	ex_index = 0;
+	occs_len = occs->len;
+	ex_occs_len = ex_occs->len;
+
+	if (ex_occs_len > 0)
+		ex_occ = &g_array_index (ex_occs, CalObjTime, ex_index);
+
+	for (i = 0; i < occs_len; i++) {
+		occ = &g_array_index (occs, CalObjTime, i);
+		keep_occ = TRUE;
+
+		/* If the occurrence is a duplicate of the previous one, skip
+		   it. */
+		if (prev_occ
+		    && cal_obj_time_compare_func (occ, prev_occ) == 0) {
+			keep_occ = FALSE;
+
+			/* If this occurrence is an RDATE with an end or
+			   duration set, and the previous occurrence in the
+			   array was kept, set the RDATE flag of the last one,
+			   so we still use the end date or duration. */
+			if (occ->flags && !current_time_is_exception) {
+				last_occ_kept = &g_array_index (occs,
+								CalObjTime,
+								j - 1);
+				last_occ_kept->flags = TRUE;
+			}
+		} else {
+			/* We've found a new occurrence time. Reset the flag
+			   to indicate that it hasn't been found in the
+			   exceptions array (yet). */
+			current_time_is_exception = FALSE;
+
+			if (ex_occ) {
+				/* Step through the exceptions until we come
+				   to one that matches or follows this
+				   occurrence. */
+				while (ex_occ) {
+					/* If the exception is an EXDATE with
+					   a DATE value, we only have to
+					   compare the date. */
+					if (ex_occ->flags)
+						cmp = cal_obj_date_only_compare_func (ex_occ, occ);
+					else
+						cmp = cal_obj_time_compare_func (ex_occ, occ);
+
+					if (cmp > 0)
+						break;
+
+					/* Move to the next exception, or set
+					   ex_occ to NULL when we reach the
+					   end of array. */
+					ex_index++;
+					if (ex_index < ex_occs_len)
+						ex_occ = &g_array_index (ex_occs, CalObjTime, ex_index);
+					else
+						ex_occ = NULL;
+
+					/* If the exception did match this
+					   occurrence we remove it, and set the
+					   flag to indicate that the current
+					   time is an exception. */
+					if (cmp == 0) {
+						current_time_is_exception = TRUE;
+						keep_occ = FALSE;
+						break;
+					}
+				}
+			}
+		}
+
+		if (keep_occ) {
+			/* We are keeping this occurrence, so we move it to
+			   the next free space, unless its position hasn't
+			   changed (i.e. all previous occurrences were also
+			   kept). */
+			if (i != j)
+				g_array_index (occs, CalObjTime, j)
+					= g_array_index (occs, CalObjTime, i);
+			j++;
+		}
+
+		prev_occ = occ;
+	}
+
+	g_array_set_size (occs, j);
+}
+
+
+
+static GArray*
+cal_obj_bysetpos_filter (ECalRecurrence *recur,
+			 GArray	    *occs)
+{
+	GArray *new_occs;
+	CalObjTime *occ;
+	GList *elem;
+	gint len, pos;
+
+	/* If BYSETPOS has not been specified, or the array is empty, just
+	   return the array. */
+	elem = recur->bysetpos;
+	if (!elem || occs->len == 0)
+		return occs;
+
+	new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
+
+	/* Iterate over the indices given in bysetpos, adding the corresponding
+	   element from occs to new_occs. */
+	len = occs->len;
+	while (elem) {
+		pos = GPOINTER_TO_INT (elem->data);
+
+		/* Negative values count back from the end of the array. */
+		if (pos < 0)
+			pos += len;
+		/* Positive values need to be decremented since the array is
+		   0-based. */
+		else
+			pos--;
+
+		if (pos >= 0 && pos < len) {
+			occ = &g_array_index (occs, CalObjTime, pos);
+			g_array_append_vals (new_occs, occ, 1);
+		}
+		elem = elem->next;
+	}
+
+	g_array_free (occs, TRUE);
+
+	return new_occs;
+}
+
+
+
+
+/* Finds the first year from the event_start, counting in multiples of the
+   recurrence interval, that intersects the given interval. It returns TRUE
+   if there is no intersection. */
+static gboolean
+cal_obj_yearly_find_start_position (CalObjTime *event_start,
+				    CalObjTime *event_end,
+				    RecurData  *recur_data,
+				    CalObjTime *interval_start,
+				    CalObjTime *interval_end,
+				    CalObjTime *cotime)
+{
+	*cotime = *event_start;
+
+	/* Move on to the next interval, if the event starts before the
+	   given interval. */
+	if (cotime->year < interval_start->year) {
+		gint years = interval_start->year - cotime->year
+			+ recur_data->recur->interval - 1;
+		years -= years % recur_data->recur->interval;
+		/* NOTE: The day may now be invalid, e.g. 29th Feb. */
+		cotime->year += years;
+	}
+
+	if ((event_end && cotime->year > event_end->year)
+	    || (interval_end && cotime->year > interval_end->year))
+		return TRUE;
+
+	return FALSE;
+}
+
+
+static gboolean
+cal_obj_yearly_find_next_position (CalObjTime *cotime,
+				   CalObjTime *event_end,
+				   RecurData  *recur_data,
+				   CalObjTime *interval_end)
+{
+	/* NOTE: The day may now be invalid, e.g. 29th Feb. */
+	cotime->year += recur_data->recur->interval;
+
+	if ((event_end && cotime->year > event_end->year)
+	    || (interval_end && cotime->year > interval_end->year))
+		return TRUE;
+
+	return FALSE;
+}
+
+
+
+static gboolean
+cal_obj_monthly_find_start_position (CalObjTime *event_start,
+				     CalObjTime *event_end,
+				     RecurData  *recur_data,
+				     CalObjTime *interval_start,
+				     CalObjTime *interval_end,
+				     CalObjTime *cotime)
+{
+	*cotime = *event_start;
+
+	/* Move on to the next interval, if the event starts before the
+	   given interval. */
+	if (cal_obj_time_compare (cotime, interval_start, CALOBJ_MONTH) < 0) {
+		gint months = (interval_start->year - cotime->year) * 12
+			+ interval_start->month - cotime->month
+			+ recur_data->recur->interval - 1;
+		months -= months % recur_data->recur->interval;
+		/* NOTE: The day may now be invalid, e.g. 31st Sep. */
+		cal_obj_time_add_months (cotime, months);
+	}
+
+	if (event_end && cal_obj_time_compare (cotime, event_end,
+					       CALOBJ_MONTH) > 0)
+		return TRUE;
+	if (interval_end && cal_obj_time_compare (cotime, interval_end,
+						  CALOBJ_MONTH) > 0)
+		return TRUE;
+
+	return FALSE;
+}
+
+
+static gboolean
+cal_obj_monthly_find_next_position (CalObjTime *cotime,
+				    CalObjTime *event_end,
+				    RecurData  *recur_data,
+				    CalObjTime *interval_end)
+{
+	/* NOTE: The day may now be invalid, e.g. 31st Sep. */
+	cal_obj_time_add_months (cotime, recur_data->recur->interval);
+
+	if (event_end && cal_obj_time_compare (cotime, event_end,
+					       CALOBJ_MONTH) > 0)
+		return TRUE;
+	if (interval_end && cal_obj_time_compare (cotime, interval_end,
+						  CALOBJ_MONTH) > 0)
+		return TRUE;
+
+	return FALSE;
+}
+
+
+
+static gboolean
+cal_obj_weekly_find_start_position (CalObjTime *event_start,
+				    CalObjTime *event_end,
+				    RecurData  *recur_data,
+				    CalObjTime *interval_start,
+				    CalObjTime *interval_end,
+				    CalObjTime *cotime)
+{
+	GDate event_start_date, interval_start_date;
+	guint32 event_start_julian, interval_start_julian;
+	gint interval_start_weekday_offset;
+	CalObjTime week_start;
+
+	if (event_end && cal_obj_time_compare (event_end, interval_start,
+					       CALOBJ_DAY) < 0)
+		return TRUE;
+	if (interval_end && cal_obj_time_compare (event_start, interval_end,
+						  CALOBJ_DAY) > 0)
+		return TRUE;
+
+	*cotime = *event_start;
+
+	/* Convert the event start and interval start to GDates, so we can
+	   easily find the number of days between them. */
+	g_date_clear (&event_start_date, 1);
+	g_date_set_dmy (&event_start_date, event_start->day,
+			event_start->month + 1, event_start->year);
+	g_date_clear (&interval_start_date, 1);
+	g_date_set_dmy (&interval_start_date, interval_start->day,
+			interval_start->month + 1, interval_start->year);
+
+	/* Calculate the start of the weeks corresponding to the event start
+	   and interval start. */
+	event_start_julian = g_date_get_julian (&event_start_date);
+	event_start_julian -= recur_data->weekday_offset;
+
+	interval_start_julian = g_date_get_julian (&interval_start_date);
+	interval_start_weekday_offset = cal_obj_time_weekday_offset (interval_start, recur_data->recur);
+	interval_start_julian -= interval_start_weekday_offset;
+
+	/* We want to find the first full week using the recurrence interval
+	   that intersects the given interval dates. */
+	if (event_start_julian < interval_start_julian) {
+		gint weeks = (interval_start_julian - event_start_julian) / 7;
+		weeks += recur_data->recur->interval - 1;
+		weeks -= weeks % recur_data->recur->interval;
+		cal_obj_time_add_days (cotime, weeks * 7);
+	}
+
+	week_start = *cotime;
+	cal_obj_time_add_days (&week_start, -recur_data->weekday_offset);
+
+	if (event_end && cal_obj_time_compare (&week_start, event_end,
+					       CALOBJ_DAY) > 0)
+		return TRUE;
+	if (interval_end && cal_obj_time_compare (&week_start, interval_end,
+						  CALOBJ_DAY) > 0)
+		return TRUE;
+
+	return FALSE;
+}
+
+
+static gboolean
+cal_obj_weekly_find_next_position (CalObjTime *cotime,
+				   CalObjTime *event_end,
+				   RecurData  *recur_data,
+				   CalObjTime *interval_end)
+{
+	CalObjTime week_start;
+
+	cal_obj_time_add_days (cotime, recur_data->recur->interval * 7);
+
+	/* Return TRUE if the start of this week is after the event finishes
+	   or is after the end of the required interval. */
+	week_start = *cotime;
+	cal_obj_time_add_days (&week_start, -recur_data->weekday_offset);
+
+#ifdef CAL_OBJ_DEBUG
+	g_print ("Next  day: %s\n", cal_obj_time_to_string (cotime));
+	g_print ("Week Start: %s\n", cal_obj_time_to_string (&week_start));
+#endif
+
+	if (event_end && cal_obj_time_compare (&week_start, event_end,
+					       CALOBJ_DAY) > 0)
+		return TRUE;
+	if (interval_end && cal_obj_time_compare (&week_start, interval_end,
+						  CALOBJ_DAY) > 0) {
+#ifdef CAL_OBJ_DEBUG
+		g_print ("Interval end reached: %s\n",
+			 cal_obj_time_to_string (interval_end));
+#endif
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+
+static gboolean
+cal_obj_daily_find_start_position  (CalObjTime *event_start,
+				    CalObjTime *event_end,
+				    RecurData  *recur_data,
+				    CalObjTime *interval_start,
+				    CalObjTime *interval_end,
+				    CalObjTime *cotime)
+{
+	GDate event_start_date, interval_start_date;
+	guint32 event_start_julian, interval_start_julian, days;
+
+	if (interval_end && cal_obj_time_compare (event_start, interval_end,
+						  CALOBJ_DAY) > 0)
+		return TRUE;
+	if (event_end && cal_obj_time_compare (event_end, interval_start,
+					       CALOBJ_DAY) < 0)
+		return TRUE;
+
+	*cotime = *event_start;
+
+	/* Convert the event start and interval start to GDates, so we can
+	   easily find the number of days between them. */
+	g_date_clear (&event_start_date, 1);
+	g_date_set_dmy (&event_start_date, event_start->day,
+			event_start->month + 1, event_start->year);
+	g_date_clear (&interval_start_date, 1);
+	g_date_set_dmy (&interval_start_date, interval_start->day,
+			interval_start->month + 1, interval_start->year);
+
+	event_start_julian = g_date_get_julian (&event_start_date);
+	interval_start_julian = g_date_get_julian (&interval_start_date);
+
+	if (event_start_julian < interval_start_julian) {
+		days = interval_start_julian - event_start_julian
+			+ recur_data->recur->interval - 1;
+		days -= days % recur_data->recur->interval;
+		cal_obj_time_add_days (cotime, days);
+	}
+
+	if (event_end && cal_obj_time_compare (cotime, event_end,
+					       CALOBJ_DAY) > 0)
+		return TRUE;
+	if (interval_end && cal_obj_time_compare (cotime, interval_end,
+						  CALOBJ_DAY) > 0)
+		return TRUE;
+
+	return FALSE;
+}
+
+
+static gboolean
+cal_obj_daily_find_next_position  (CalObjTime *cotime,
+				   CalObjTime *event_end,
+				   RecurData  *recur_data,
+				   CalObjTime *interval_end)
+{
+	cal_obj_time_add_days (cotime, recur_data->recur->interval);
+
+	if (event_end && cal_obj_time_compare (cotime, event_end,
+					       CALOBJ_DAY) > 0)
+		return TRUE;
+	if (interval_end && cal_obj_time_compare (cotime, interval_end,
+						  CALOBJ_DAY) > 0)
+		return TRUE;
+
+	return FALSE;
+}
+
+
+static gboolean
+cal_obj_hourly_find_start_position (CalObjTime *event_start,
+				    CalObjTime *event_end,
+				    RecurData  *recur_data,
+				    CalObjTime *interval_start,
+				    CalObjTime *interval_end,
+				    CalObjTime *cotime)
+{
+	GDate event_start_date, interval_start_date;
+	guint32 event_start_julian, interval_start_julian, hours;
+
+	if (interval_end && cal_obj_time_compare (event_start, interval_end,
+						  CALOBJ_HOUR) > 0)
+		return TRUE;
+	if (event_end && cal_obj_time_compare (event_end, interval_start,
+					       CALOBJ_HOUR) < 0)
+		return TRUE;
+
+	*cotime = *event_start;
+
+	if (cal_obj_time_compare (event_start, interval_start,
+				  CALOBJ_HOUR) < 0) {
+		/* Convert the event start and interval start to GDates, so we
+		   can easily find the number of days between them. */
+		g_date_clear (&event_start_date, 1);
+		g_date_set_dmy (&event_start_date, event_start->day,
+				event_start->month + 1, event_start->year);
+		g_date_clear (&interval_start_date, 1);
+		g_date_set_dmy (&interval_start_date, interval_start->day,
+				interval_start->month + 1,
+				interval_start->year);
+
+		event_start_julian = g_date_get_julian (&event_start_date);
+		interval_start_julian = g_date_get_julian (&interval_start_date);
+
+		hours = (interval_start_julian - event_start_julian) * 24;
+		hours += interval_start->hour - event_start->hour;
+		hours += recur_data->recur->interval - 1;
+		hours -= hours % recur_data->recur->interval;
+		cal_obj_time_add_hours (cotime, hours);
+	}
+
+	if (event_end && cal_obj_time_compare (cotime, event_end,
+					       CALOBJ_HOUR) > 0)
+		return TRUE;
+	if (interval_end && cal_obj_time_compare (cotime, interval_end,
+						  CALOBJ_HOUR) > 0)
+		return TRUE;
+
+	return FALSE;
+}
+
+
+static gboolean
+cal_obj_hourly_find_next_position (CalObjTime *cotime,
+				   CalObjTime *event_end,
+				   RecurData  *recur_data,
+				   CalObjTime *interval_end)
+{
+	cal_obj_time_add_hours (cotime, recur_data->recur->interval);
+
+	if (event_end && cal_obj_time_compare (cotime, event_end,
+					       CALOBJ_HOUR) > 0)
+		return TRUE;
+	if (interval_end && cal_obj_time_compare (cotime, interval_end,
+						  CALOBJ_HOUR) > 0)
+		return TRUE;
+
+	return FALSE;
+}
+
+
+static gboolean
+cal_obj_minutely_find_start_position (CalObjTime *event_start,
+				      CalObjTime *event_end,
+				      RecurData  *recur_data,
+				      CalObjTime *interval_start,
+				      CalObjTime *interval_end,
+				      CalObjTime *cotime)
+{
+	GDate event_start_date, interval_start_date;
+	guint32 event_start_julian, interval_start_julian, minutes;
+
+	if (interval_end && cal_obj_time_compare (event_start, interval_end,
+						  CALOBJ_MINUTE) > 0)
+		return TRUE;
+	if (event_end && cal_obj_time_compare (event_end, interval_start,
+					       CALOBJ_MINUTE) < 0)
+		return TRUE;
+
+	*cotime = *event_start;
+
+	if (cal_obj_time_compare (event_start, interval_start,
+				  CALOBJ_MINUTE) < 0) {
+		/* Convert the event start and interval start to GDates, so we
+		   can easily find the number of days between them. */
+		g_date_clear (&event_start_date, 1);
+		g_date_set_dmy (&event_start_date, event_start->day,
+				event_start->month + 1, event_start->year);
+		g_date_clear (&interval_start_date, 1);
+		g_date_set_dmy (&interval_start_date, interval_start->day,
+				interval_start->month + 1,
+				interval_start->year);
+
+		event_start_julian = g_date_get_julian (&event_start_date);
+		interval_start_julian = g_date_get_julian (&interval_start_date);
+
+		minutes = (interval_start_julian - event_start_julian)
+			* 24 * 60;
+		minutes += (interval_start->hour - event_start->hour) * 24;
+		minutes += interval_start->minute - event_start->minute;
+		minutes += recur_data->recur->interval - 1;
+		minutes -= minutes % recur_data->recur->interval;
+		cal_obj_time_add_minutes (cotime, minutes);
+	}
+
+	if (event_end && cal_obj_time_compare (cotime, event_end,
+					       CALOBJ_MINUTE) > 0)
+		return TRUE;
+	if (interval_end && cal_obj_time_compare (cotime, interval_end,
+						  CALOBJ_MINUTE) > 0)
+		return TRUE;
+
+	return FALSE;
+}
+
+
+static gboolean
+cal_obj_minutely_find_next_position (CalObjTime *cotime,
+				     CalObjTime *event_end,
+				     RecurData  *recur_data,
+				     CalObjTime *interval_end)
+{
+	cal_obj_time_add_minutes (cotime, recur_data->recur->interval);
+
+	if (event_end && cal_obj_time_compare (cotime, event_end,
+					       CALOBJ_MINUTE) > 0)
+		return TRUE;
+	if (interval_end && cal_obj_time_compare (cotime, interval_end,
+						  CALOBJ_MINUTE) > 0)
+		return TRUE;
+
+	return FALSE;
+}
+
+
+static gboolean
+cal_obj_secondly_find_start_position (CalObjTime *event_start,
+				      CalObjTime *event_end,
+				      RecurData  *recur_data,
+				      CalObjTime *interval_start,
+				      CalObjTime *interval_end,
+				      CalObjTime *cotime)
+{
+	GDate event_start_date, interval_start_date;
+	guint32 event_start_julian, interval_start_julian, seconds;
+
+	if (interval_end && cal_obj_time_compare (event_start, interval_end,
+						  CALOBJ_SECOND) > 0)
+		return TRUE;
+	if (event_end && cal_obj_time_compare (event_end, interval_start,
+					       CALOBJ_SECOND) < 0)
+		return TRUE;
+
+	*cotime = *event_start;
+
+	if (cal_obj_time_compare (event_start, interval_start,
+				  CALOBJ_SECOND) < 0) {
+		/* Convert the event start and interval start to GDates, so we
+		   can easily find the number of days between them. */
+		g_date_clear (&event_start_date, 1);
+		g_date_set_dmy (&event_start_date, event_start->day,
+				event_start->month + 1, event_start->year);
+		g_date_clear (&interval_start_date, 1);
+		g_date_set_dmy (&interval_start_date, interval_start->day,
+				interval_start->month + 1,
+				interval_start->year);
+
+		event_start_julian = g_date_get_julian (&event_start_date);
+		interval_start_julian = g_date_get_julian (&interval_start_date);
+
+		seconds = (interval_start_julian - event_start_julian)
+			* 24 * 60 * 60;
+		seconds += (interval_start->hour - event_start->hour)
+			* 24 * 60;
+		seconds += (interval_start->minute - event_start->minute) * 60;
+		seconds += interval_start->second - event_start->second;
+		seconds += recur_data->recur->interval - 1;
+		seconds -= seconds % recur_data->recur->interval;
+		cal_obj_time_add_seconds (cotime, seconds);
+	}
+
+	if (event_end && cal_obj_time_compare (cotime, event_end,
+					       CALOBJ_SECOND) >= 0)
+		return TRUE;
+	if (interval_end && cal_obj_time_compare (cotime, interval_end,
+						  CALOBJ_SECOND) >= 0)
+		return TRUE;
+
+	return FALSE;
+}
+
+
+static gboolean
+cal_obj_secondly_find_next_position (CalObjTime *cotime,
+				     CalObjTime *event_end,
+				     RecurData  *recur_data,
+				     CalObjTime *interval_end)
+{
+	cal_obj_time_add_seconds (cotime, recur_data->recur->interval);
+
+	if (event_end && cal_obj_time_compare (cotime, event_end,
+					       CALOBJ_SECOND) >= 0)
+		return TRUE;
+	if (interval_end && cal_obj_time_compare (cotime, interval_end,
+						  CALOBJ_SECOND) >= 0)
+		return TRUE;
+
+	return FALSE;
+}
+
+
+
+
+
+/* If the BYMONTH rule is specified it expands each occurrence in occs, by
+   using each of the months in the bymonth list. */
+static GArray*
+cal_obj_bymonth_expand		(RecurData  *recur_data,
+				 GArray     *occs)
+{
+	GArray *new_occs;
+	CalObjTime *occ;
+	GList *elem;
+	gint len, i;
+
+	/* If BYMONTH has not been specified, or the array is empty, just
+	   return the array. */
+	if (!recur_data->recur->bymonth || occs->len == 0)
+		return occs;
+
+	new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
+
+	len = occs->len;
+	for (i = 0; i < len; i++) {
+		occ = &g_array_index (occs, CalObjTime, i);
+
+		elem = recur_data->recur->bymonth;
+		while (elem) {
+			/* NOTE: The day may now be invalid, e.g. 31st Feb. */
+			occ->month = GPOINTER_TO_INT (elem->data);
+			g_array_append_vals (new_occs, occ, 1);
+			elem = elem->next;
+		}
+	}
+
+	g_array_free (occs, TRUE);
+
+	return new_occs;
+}
+
+
+/* If the BYMONTH rule is specified it filters out all occurrences in occs
+   which do not match one of the months in the bymonth list. */
+static GArray*
+cal_obj_bymonth_filter		(RecurData  *recur_data,
+				 GArray     *occs)
+{
+	GArray *new_occs;
+	CalObjTime *occ;
+	gint len, i;
+
+	/* If BYMONTH has not been specified, or the array is empty, just
+	   return the array. */
+	if (!recur_data->recur->bymonth || occs->len == 0)
+		return occs;
+
+	new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
+
+	len = occs->len;
+	for (i = 0; i < len; i++) {
+		occ = &g_array_index (occs, CalObjTime, i);
+		if (recur_data->months[occ->month])
+			g_array_append_vals (new_occs, occ, 1);
+	}
+
+	g_array_free (occs, TRUE);
+
+	return new_occs;
+}
+
+
+
+static GArray*
+cal_obj_byweekno_expand		(RecurData  *recur_data,
+				 GArray     *occs)
+{
+	GArray *new_occs;
+	CalObjTime *occ, year_start_cotime, year_end_cotime, cotime;
+	GList *elem;
+	gint len, i, weekno;
+
+	/* If BYWEEKNO has not been specified, or the array is empty, just
+	   return the array. */
+	if (!recur_data->recur->byweekno || occs->len == 0)
+		return occs;
+
+	new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
+
+	len = occs->len;
+	for (i = 0; i < len; i++) {
+		occ = &g_array_index (occs, CalObjTime, i);
+
+		/* Find the day that would correspond to week 1 (note that
+		   week 1 is the first week starting from the specified week
+		   start day that has 4 days in the new year). */
+		year_start_cotime = *occ;
+		cal_obj_time_find_first_week (&year_start_cotime,
+					      recur_data);
+
+		/* Find the day that would correspond to week 1 of the next
+		   year, which we use for -ve week numbers. */
+		year_end_cotime = *occ;
+		year_end_cotime.year++;
+		cal_obj_time_find_first_week (&year_end_cotime,
+					      recur_data);
+
+		/* Now iterate over the week numbers in byweekno, generating a
+		   new occurrence for each one. */
+		elem = recur_data->recur->byweekno;
+		while (elem) {
+			weekno = GPOINTER_TO_INT (elem->data);
+			if (weekno > 0) {
+				cotime = year_start_cotime;
+				cal_obj_time_add_days (&cotime,
+						       (weekno - 1) * 7);
+			} else {
+				cotime = year_end_cotime;
+				cal_obj_time_add_days (&cotime, weekno * 7);
+			}
+
+			/* Skip occurrences if they fall outside the year. */
+			if (cotime.year == occ->year)
+				g_array_append_val (new_occs, cotime);
+			elem = elem->next;
+		}
+	}
+
+	g_array_free (occs, TRUE);
+
+	return new_occs;
+}
+
+
+#if 0
+/* This isn't used at present. */
+static GArray*
+cal_obj_byweekno_filter		(RecurData  *recur_data,
+				 GArray     *occs)
+{
+
+	return occs;
+}
+#endif
+
+
+static GArray*
+cal_obj_byyearday_expand	(RecurData  *recur_data,
+				 GArray     *occs)
+{
+	GArray *new_occs;
+	CalObjTime *occ, year_start_cotime, year_end_cotime, cotime;
+	GList *elem;
+	gint len, i, dayno;
+
+	/* If BYYEARDAY has not been specified, or the array is empty, just
+	   return the array. */
+	if (!recur_data->recur->byyearday || occs->len == 0)
+		return occs;
+
+	new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
+
+	len = occs->len;
+	for (i = 0; i < len; i++) {
+		occ = &g_array_index (occs, CalObjTime, i);
+
+		/* Find the day that would correspond to day 1. */
+		year_start_cotime = *occ;
+		year_start_cotime.month = 0;
+		year_start_cotime.day = 1;
+
+		/* Find the day that would correspond to day 1 of the next
+		   year, which we use for -ve day numbers. */
+		year_end_cotime = *occ;
+		year_end_cotime.year++;
+		year_end_cotime.month = 0;
+		year_end_cotime.day = 1;
+
+		/* Now iterate over the day numbers in byyearday, generating a
+		   new occurrence for each one. */
+		elem = recur_data->recur->byyearday;
+		while (elem) {
+			dayno = GPOINTER_TO_INT (elem->data);
+			if (dayno > 0) {
+				cotime = year_start_cotime;
+				cal_obj_time_add_days (&cotime, dayno - 1);
+			} else {
+				cotime = year_end_cotime;
+				cal_obj_time_add_days (&cotime, dayno);
+			}
+
+			/* Skip occurrences if they fall outside the year. */
+			if (cotime.year == occ->year)
+				g_array_append_val (new_occs, cotime);
+			elem = elem->next;
+		}
+	}
+
+	g_array_free (occs, TRUE);
+
+	return new_occs;
+}
+
+
+/* Note: occs must not contain invalid dates, e.g. 31st September. */
+static GArray*
+cal_obj_byyearday_filter	(RecurData  *recur_data,
+				 GArray     *occs)
+{
+	GArray *new_occs;
+	CalObjTime *occ;
+	gint yearday, len, i, days_in_year;
+
+	/* If BYYEARDAY has not been specified, or the array is empty, just
+	   return the array. */
+	if (!recur_data->recur->byyearday || occs->len == 0)
+		return occs;
+
+	new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
+
+	len = occs->len;
+	for (i = 0; i < len; i++) {
+		occ = &g_array_index (occs, CalObjTime, i);
+		yearday = cal_obj_time_day_of_year (occ);
+		if (recur_data->yeardays[yearday]) {
+			g_array_append_vals (new_occs, occ, 1);
+		} else {
+			days_in_year = g_date_is_leap_year (occ->year)
+				? 366 : 365;
+			if (recur_data->neg_yeardays[days_in_year + 1
+						    - yearday])
+				g_array_append_vals (new_occs, occ, 1);
+		}
+	}
+
+	g_array_free (occs, TRUE);
+
+	return new_occs;
+}
+
+
+
+static GArray*
+cal_obj_bymonthday_expand	(RecurData  *recur_data,
+				 GArray     *occs)
+{
+	GArray *new_occs;
+	CalObjTime *occ, month_start_cotime, month_end_cotime, cotime;
+	GList *elem;
+	gint len, i, dayno;
+
+	/* If BYMONTHDAY has not been specified, or the array is empty, just
+	   return the array. */
+	if (!recur_data->recur->bymonthday || occs->len == 0)
+		return occs;
+
+	new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
+
+	len = occs->len;
+	for (i = 0; i < len; i++) {
+		occ = &g_array_index (occs, CalObjTime, i);
+
+		/* Find the day that would correspond to day 1. */
+		month_start_cotime = *occ;
+		month_start_cotime.day = 1;
+
+		/* Find the day that would correspond to day 1 of the next
+		   month, which we use for -ve day numbers. */
+		month_end_cotime = *occ;
+		month_end_cotime.month++;
+		month_end_cotime.day = 1;
+
+		/* Now iterate over the day numbers in bymonthday, generating a
+		   new occurrence for each one. */
+		elem = recur_data->recur->bymonthday;
+		while (elem) {
+			dayno = GPOINTER_TO_INT (elem->data);
+			if (dayno > 0) {
+				cotime = month_start_cotime;
+				cal_obj_time_add_days (&cotime, dayno - 1);
+			} else {
+				cotime = month_end_cotime;
+				cal_obj_time_add_days (&cotime, dayno);
+			}
+			if (cotime.month == occ->month) {
+				g_array_append_val (new_occs, cotime);
+			} else {
+				/* set to last day in month */
+				cotime.month = occ->month;
+				cotime.day = time_days_in_month (occ->year, occ->month);
+				g_array_append_val (new_occs, cotime);
+			}
+				
+			elem = elem->next;
+		}
+	}
+
+	g_array_free (occs, TRUE);
+
+	return new_occs;
+}
+
+
+static GArray*
+cal_obj_bymonthday_filter	(RecurData  *recur_data,
+				 GArray     *occs)
+{
+	GArray *new_occs;
+	CalObjTime *occ;
+	gint len, i, days_in_month;
+
+	/* If BYMONTHDAY has not been specified, or the array is empty, just
+	   return the array. */
+	if (!recur_data->recur->bymonthday || occs->len == 0)
+		return occs;
+
+	new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
+
+	len = occs->len;
+	for (i = 0; i < len; i++) {
+		occ = &g_array_index (occs, CalObjTime, i);
+		if (recur_data->monthdays[occ->day]) {
+			g_array_append_vals (new_occs, occ, 1);
+		} else {
+			days_in_month = time_days_in_month (occ->year,
+							    occ->month);
+			if (recur_data->neg_monthdays[days_in_month + 1
+						     - occ->day])
+				g_array_append_vals (new_occs, occ, 1);
+		}
+	}
+
+	g_array_free (occs, TRUE);
+
+	return new_occs;
+}
+
+
+
+static GArray*
+cal_obj_byday_expand_yearly	(RecurData  *recur_data,
+				 GArray     *occs)
+{
+	GArray *new_occs;
+	CalObjTime *occ;
+	GList *elem;
+	gint len, i, weekday, week_num;
+	gint first_weekday, last_weekday, offset;
+	guint16 year;
+
+	/* If BYDAY has not been specified, or the array is empty, just
+	   return the array. */
+	if (!recur_data->recur->byday || occs->len == 0)
+		return occs;
+
+	new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
+
+	len = occs->len;
+	for (i = 0; i < len; i++) {
+		occ = &g_array_index (occs, CalObjTime, i);
+
+		elem = recur_data->recur->byday;
+		while (elem) {
+			weekday = GPOINTER_TO_INT (elem->data);
+			elem = elem->next;
+			week_num = GPOINTER_TO_INT (elem->data);
+			elem = elem->next;
+
+			year = occ->year;
+			if (week_num == 0) {
+				/* Expand to every Mon/Tue/etc. in the year. */
+				occ->month = 0;
+				occ->day = 1;
+				first_weekday = cal_obj_time_weekday (occ);
+				offset = (weekday + 7 - first_weekday) % 7;
+				cal_obj_time_add_days (occ, offset);
+
+				while (occ->year == year) {
+					g_array_append_vals (new_occs, occ, 1);
+					cal_obj_time_add_days (occ, 7);
+				}
+
+			} else if (week_num > 0) {
+				/* Add the nth Mon/Tue/etc. in the year. */
+				occ->month = 0;
+				occ->day = 1;
+				first_weekday = cal_obj_time_weekday (occ);
+				offset = (weekday + 7 - first_weekday) % 7;
+				offset += (week_num - 1) * 7;
+				cal_obj_time_add_days (occ, offset);
+				if (occ->year == year)
+					g_array_append_vals (new_occs, occ, 1);
+
+			} else {
+				/* Add the -nth Mon/Tue/etc. in the year. */
+				occ->month = 11;
+				occ->day = 31;
+				last_weekday = cal_obj_time_weekday (occ);
+				offset = (last_weekday + 7 - weekday) % 7;
+				offset += (week_num - 1) * 7;
+				cal_obj_time_add_days (occ, -offset);
+				if (occ->year == year)
+					g_array_append_vals (new_occs, occ, 1);
+			}
+
+			/* Reset the year, as we may have gone past the end. */
+			occ->year = year;
+		}
+	}
+
+	g_array_free (occs, TRUE);
+
+	return new_occs;
+}
+
+
+static GArray*
+cal_obj_byday_expand_monthly	(RecurData  *recur_data,
+				 GArray     *occs)
+{
+	GArray *new_occs;
+	CalObjTime *occ;
+	GList *elem;
+	gint len, i, weekday, week_num;
+	gint first_weekday, last_weekday, offset;
+	guint16 year;
+	guint8 month;
+
+	/* If BYDAY has not been specified, or the array is empty, just
+	   return the array. */
+	if (!recur_data->recur->byday || occs->len == 0)
+		return occs;
+
+	new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
+
+	len = occs->len;
+	for (i = 0; i < len; i++) {
+		occ = &g_array_index (occs, CalObjTime, i);
+
+		elem = recur_data->recur->byday;
+		while (elem) {
+			weekday = GPOINTER_TO_INT (elem->data);
+			elem = elem->next;
+			week_num = GPOINTER_TO_INT (elem->data);
+			elem = elem->next;
+
+			year = occ->year;
+			month = occ->month;
+			if (week_num == 0) {
+				/* Expand to every Mon/Tue/etc. in the month.*/
+				occ->day = 1;
+				first_weekday = cal_obj_time_weekday (occ);
+				offset = (weekday + 7 - first_weekday) % 7;
+				cal_obj_time_add_days (occ, offset);
+
+				while (occ->year == year
+				       && occ->month == month) {
+					g_array_append_vals (new_occs, occ, 1);
+					cal_obj_time_add_days (occ, 7);
+				}
+
+			} else if (week_num > 0) {
+				/* Add the nth Mon/Tue/etc. in the month. */
+				occ->day = 1;
+				first_weekday = cal_obj_time_weekday (occ);
+				offset = (weekday + 7 - first_weekday) % 7;
+				offset += (week_num - 1) * 7;
+				cal_obj_time_add_days (occ, offset);
+				if (occ->year == year && occ->month == month)
+					g_array_append_vals (new_occs, occ, 1);
+
+			} else {
+				/* Add the -nth Mon/Tue/etc. in the month. */
+				occ->day = time_days_in_month (occ->year,
+							       occ->month);
+				last_weekday = cal_obj_time_weekday (occ);
+
+				/* This calculates the number of days to step
+				   backwards from the last day of the month
+				   to the weekday we want. */
+				offset = (last_weekday + 7 - weekday) % 7;
+
+				/* This adds on the weeks. */
+				offset += (-week_num - 1) * 7;
+
+				cal_obj_time_add_days (occ, -offset);
+				if (occ->year == year && occ->month == month)
+					g_array_append_vals (new_occs, occ, 1);
+			}
+
+			/* Reset the year & month, as we may have gone past
+			   the end. */
+			occ->year = year;
+			occ->month = month;
+		}
+	}
+
+	g_array_free (occs, TRUE);
+
+	return new_occs;
+}
+
+
+/* Note: occs must not contain invalid dates, e.g. 31st September. */
+static GArray*
+cal_obj_byday_expand_weekly	(RecurData  *recur_data,
+				 GArray     *occs)
+{
+	GArray *new_occs;
+	CalObjTime *occ;
+	GList *elem;
+	gint len, i, weekday, week_num;
+	gint weekday_offset, new_weekday_offset;
+
+	/* If BYDAY has not been specified, or the array is empty, just
+	   return the array. */
+	if (!recur_data->recur->byday || occs->len == 0)
+		return occs;
+
+	new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
+
+	len = occs->len;
+	for (i = 0; i < len; i++) {
+		occ = &g_array_index (occs, CalObjTime, i);
+
+		elem = recur_data->recur->byday;
+		while (elem) {
+			weekday = GPOINTER_TO_INT (elem->data);
+			elem = elem->next;
+
+			/* FIXME: Currently we just ignore this, but maybe we
+			   should skip all elements where week_num != 0.
+			   The spec isn't clear about this. */
+			week_num = GPOINTER_TO_INT (elem->data);
+			elem = elem->next;
+
+			weekday_offset = cal_obj_time_weekday_offset (occ, recur_data->recur);
+			new_weekday_offset = (weekday + 7 - recur_data->recur->week_start_day) % 7;
+			cal_obj_time_add_days (occ, new_weekday_offset - weekday_offset);
+			g_array_append_vals (new_occs, occ, 1);
+		}
+	}
+
+	g_array_free (occs, TRUE);
+
+	return new_occs;
+}
+
+
+/* Note: occs must not contain invalid dates, e.g. 31st September. */
+static GArray*
+cal_obj_byday_filter		(RecurData  *recur_data,
+				 GArray     *occs)
+{
+	GArray *new_occs;
+	CalObjTime *occ;
+	gint len, i, weekday;
+
+	/* If BYDAY has not been specified, or the array is empty, just
+	   return the array. */
+	if (!recur_data->recur->byday || occs->len == 0)
+		return occs;
+
+	new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
+
+	len = occs->len;
+	for (i = 0; i < len; i++) {
+		occ = &g_array_index (occs, CalObjTime, i);
+		weekday = cal_obj_time_weekday (occ);
+
+		/* See if the weekday on its own is set. */
+		if (recur_data->weekdays[weekday])
+			g_array_append_vals (new_occs, occ, 1);
+	}
+
+	g_array_free (occs, TRUE);
+
+	return new_occs;
+}
+
+
+
+/* If the BYHOUR rule is specified it expands each occurrence in occs, by
+   using each of the hours in the byhour list. */
+static GArray*
+cal_obj_byhour_expand		(RecurData  *recur_data,
+				 GArray     *occs)
+{
+	GArray *new_occs;
+	CalObjTime *occ;
+	GList *elem;
+	gint len, i;
+
+	/* If BYHOUR has not been specified, or the array is empty, just
+	   return the array. */
+	if (!recur_data->recur->byhour || occs->len == 0)
+		return occs;
+
+	new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
+
+	len = occs->len;
+	for (i = 0; i < len; i++) {
+		occ = &g_array_index (occs, CalObjTime, i);
+
+		elem = recur_data->recur->byhour;
+		while (elem) {
+			occ->hour = GPOINTER_TO_INT (elem->data);
+			g_array_append_vals (new_occs, occ, 1);
+			elem = elem->next;
+		}
+	}
+
+	g_array_free (occs, TRUE);
+
+	return new_occs;
+}
+
+
+/* If the BYHOUR rule is specified it filters out all occurrences in occs
+   which do not match one of the hours in the byhour list. */
+static GArray*
+cal_obj_byhour_filter		(RecurData  *recur_data,
+				 GArray     *occs)
+{
+	GArray *new_occs;
+	CalObjTime *occ;
+	gint len, i;
+
+	/* If BYHOUR has not been specified, or the array is empty, just
+	   return the array. */
+	if (!recur_data->recur->byhour || occs->len == 0)
+		return occs;
+
+	new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
+
+	len = occs->len;
+	for (i = 0; i < len; i++) {
+		occ = &g_array_index (occs, CalObjTime, i);
+		if (recur_data->hours[occ->hour])
+			g_array_append_vals (new_occs, occ, 1);
+	}
+
+	g_array_free (occs, TRUE);
+
+	return new_occs;
+}
+
+
+
+/* If the BYMINUTE rule is specified it expands each occurrence in occs, by
+   using each of the minutes in the byminute list. */
+static GArray*
+cal_obj_byminute_expand		(RecurData  *recur_data,
+				 GArray     *occs)
+{
+	GArray *new_occs;
+	CalObjTime *occ;
+	GList *elem;
+	gint len, i;
+
+	/* If BYMINUTE has not been specified, or the array is empty, just
+	   return the array. */
+	if (!recur_data->recur->byminute || occs->len == 0)
+		return occs;
+
+	new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
+
+	len = occs->len;
+	for (i = 0; i < len; i++) {
+		occ = &g_array_index (occs, CalObjTime, i);
+
+		elem = recur_data->recur->byminute;
+		while (elem) {
+			occ->minute = GPOINTER_TO_INT (elem->data);
+			g_array_append_vals (new_occs, occ, 1);
+			elem = elem->next;
+		}
+	}
+
+	g_array_free (occs, TRUE);
+
+	return new_occs;
+}
+
+
+/* If the BYMINUTE rule is specified it filters out all occurrences in occs
+   which do not match one of the minutes in the byminute list. */
+static GArray*
+cal_obj_byminute_filter		(RecurData  *recur_data,
+				 GArray     *occs)
+{
+	GArray *new_occs;
+	CalObjTime *occ;
+	gint len, i;
+
+	/* If BYMINUTE has not been specified, or the array is empty, just
+	   return the array. */
+	if (!recur_data->recur->byminute || occs->len == 0)
+		return occs;
+
+	new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
+
+	len = occs->len;
+	for (i = 0; i < len; i++) {
+		occ = &g_array_index (occs, CalObjTime, i);
+		if (recur_data->minutes[occ->minute])
+			g_array_append_vals (new_occs, occ, 1);
+	}
+
+	g_array_free (occs, TRUE);
+
+	return new_occs;
+}
+
+
+
+/* If the BYSECOND rule is specified it expands each occurrence in occs, by
+   using each of the seconds in the bysecond list. */
+static GArray*
+cal_obj_bysecond_expand		(RecurData  *recur_data,
+				 GArray     *occs)
+{
+	GArray *new_occs;
+	CalObjTime *occ;
+	GList *elem;
+	gint len, i;
+
+	/* If BYSECOND has not been specified, or the array is empty, just
+	   return the array. */
+	if (!recur_data->recur->bysecond || occs->len == 0)
+		return occs;
+
+	new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
+
+	len = occs->len;
+	for (i = 0; i < len; i++) {
+		occ = &g_array_index (occs, CalObjTime, i);
+
+		elem = recur_data->recur->bysecond;
+		while (elem) {
+			occ->second = GPOINTER_TO_INT (elem->data);
+			g_array_append_vals (new_occs, occ, 1);
+			elem = elem->next;
+		}
+	}
+
+	g_array_free (occs, TRUE);
+
+	return new_occs;
+}
+
+
+/* If the BYSECOND rule is specified it filters out all occurrences in occs
+   which do not match one of the seconds in the bysecond list. */
+static GArray*
+cal_obj_bysecond_filter		(RecurData  *recur_data,
+				 GArray     *occs)
+{
+	GArray *new_occs;
+	CalObjTime *occ;
+	gint len, i;
+
+	/* If BYSECOND has not been specified, or the array is empty, just
+	   return the array. */
+	if (!recur_data->recur->bysecond || occs->len == 0)
+		return occs;
+
+	new_occs = g_array_new (FALSE, FALSE, sizeof (CalObjTime));
+
+	len = occs->len;
+	for (i = 0; i < len; i++) {
+		occ = &g_array_index (occs, CalObjTime, i);
+		if (recur_data->seconds[occ->second])
+			g_array_append_vals (new_occs, occ, 1);
+	}
+
+	g_array_free (occs, TRUE);
+
+	return new_occs;
+}
+
+
+
+
+
+/* Adds a positive or negative number of months to the given CalObjTime,
+   updating the year appropriately so we end up with a valid month.
+   Note that the day may be invalid, e.g. 30th Feb. */
+static void
+cal_obj_time_add_months		(CalObjTime *cotime,
+				 gint	     months)
+{
+	guint month, years;
+
+	/* We use a guint to avoid overflow on the guint8. */
+	month = cotime->month + months;
+	cotime->month = month % 12;
+	if (month > 0) {
+		cotime->year += month / 12;
+	} else {
+		years = month / 12;
+		if (cotime->month != 0) {
+			cotime->month += 12;
+			years -= 1;
+		}
+		cotime->year += years;
+	}
+}
+
+
+/* Adds a positive or negative number of days to the given CalObjTime,
+   updating the month and year appropriately so we end up with a valid day. */
+static void
+cal_obj_time_add_days		(CalObjTime *cotime,
+				 gint	     days)
+{
+	gint day, days_in_month;
+
+	/* We use a guint to avoid overflow on the guint8. */
+	day = cotime->day;
+	day += days;
+
+	if (days >= 0) {
+		for (;;) {
+			days_in_month = time_days_in_month (cotime->year,
+							    cotime->month);
+			if (day <= days_in_month)
+				break;
+
+			cotime->month++;
+			if (cotime->month >= 12) {
+				cotime->year++;
+				cotime->month = 0;
+			}
+
+			day -= days_in_month;
+		}
+
+		cotime->day = (guint8) day;
+	} else {
+		while (day <= 0) {
+			if (cotime->month == 0) {
+				cotime->year--;
+				cotime->month = 11;
+			} else {
+				cotime->month--;
+			}
+
+			days_in_month = time_days_in_month (cotime->year,
+							    cotime->month);
+			day += days_in_month;
+		}
+
+		cotime->day = (guint8) day;
+	}
+}
+
+
+/* Adds a positive or negative number of hours to the given CalObjTime,
+   updating the day, month & year appropriately so we end up with a valid
+   time. */
+static void
+cal_obj_time_add_hours		(CalObjTime *cotime,
+				 gint	     hours)
+{
+	gint hour, days;
+
+	/* We use a gint to avoid overflow on the guint8. */
+	hour = cotime->hour + hours;
+	cotime->hour = hour % 24;
+	if (hour >= 0) {
+		if (hour >= 24)
+			cal_obj_time_add_days (cotime, hour / 24);
+	} else {
+		days = hour / 24;
+		if (cotime->hour != 0) {
+			cotime->hour += 24;
+			days -= 1;
+		}
+		cal_obj_time_add_days (cotime, days);
+	}
+}
+
+
+/* Adds a positive or negative number of minutes to the given CalObjTime,
+   updating the rest of the CalObjTime appropriately. */
+static void
+cal_obj_time_add_minutes	(CalObjTime *cotime,
+				 gint	     minutes)
+{
+	gint minute, hours;
+
+	/* We use a gint to avoid overflow on the guint8. */
+	minute = cotime->minute + minutes;
+	cotime->minute = minute % 60;
+	if (minute >= 0) {
+		if (minute >= 60)
+			cal_obj_time_add_hours (cotime, minute / 60);
+	} else {
+		hours = minute / 60;
+		if (cotime->minute != 0) {
+			cotime->minute += 60;
+			hours -= 1;
+		}
+		cal_obj_time_add_hours (cotime, hours);
+	}
+}
+
+
+/* Adds a positive or negative number of seconds to the given CalObjTime,
+   updating the rest of the CalObjTime appropriately. */
+static void
+cal_obj_time_add_seconds	(CalObjTime *cotime,
+				 gint	     seconds)
+{
+	gint second, minutes;
+
+	/* We use a gint to avoid overflow on the guint8. */
+	second = cotime->second + seconds;
+	cotime->second = second % 60;
+	if (second >= 0) {
+		if (second >= 60)
+			cal_obj_time_add_minutes (cotime, second / 60);
+	} else {
+		minutes = second / 60;
+		if (cotime->second != 0) {
+			cotime->second += 60;
+			minutes -= 1;
+		}
+		cal_obj_time_add_minutes (cotime, minutes);
+	}
+}
+
+
+/* Compares 2 CalObjTimes. Returns -1 if the cotime1 is before cotime2, 0 if
+   they are the same, or 1 if cotime1 is after cotime2. The comparison type
+   specifies which parts of the times we are interested in, e.g. if CALOBJ_DAY
+   is used we only want to know if the days are different. */
+static gint
+cal_obj_time_compare		(CalObjTime *cotime1,
+				 CalObjTime *cotime2,
+				 CalObjTimeComparison type)
+{
+	if (cotime1->year < cotime2->year)
+		return -1;
+	if (cotime1->year > cotime2->year)
+		return 1;
+
+	if (type == CALOBJ_YEAR)
+		return 0;
+
+	if (cotime1->month < cotime2->month)
+		return -1;
+	if (cotime1->month > cotime2->month)
+		return 1;
+
+	if (type == CALOBJ_MONTH)
+		return 0;
+
+	if (cotime1->day < cotime2->day)
+		return -1;
+	if (cotime1->day > cotime2->day)
+		return 1;
+
+	if (type == CALOBJ_DAY)
+		return 0;
+
+	if (cotime1->hour < cotime2->hour)
+		return -1;
+	if (cotime1->hour > cotime2->hour)
+		return 1;
+
+	if (type == CALOBJ_HOUR)
+		return 0;
+
+	if (cotime1->minute < cotime2->minute)
+		return -1;
+	if (cotime1->minute > cotime2->minute)
+		return 1;
+
+	if (type == CALOBJ_MINUTE)
+		return 0;
+
+	if (cotime1->second < cotime2->second)
+		return -1;
+	if (cotime1->second > cotime2->second)
+		return 1;
+
+	return 0;
+}
+
+
+/* This is the same as the above function, but without the comparison type.
+   It is used for qsort(). */
+static gint
+cal_obj_time_compare_func (const void *arg1,
+			   const void *arg2)
+{
+	CalObjTime *cotime1, *cotime2;
+	gint retval;
+
+	cotime1 = (CalObjTime*) arg1;
+	cotime2 = (CalObjTime*) arg2;
+
+	if (cotime1->year < cotime2->year)
+		retval = -1;
+	else if (cotime1->year > cotime2->year)
+		retval = 1;
+
+	else if (cotime1->month < cotime2->month)
+		retval = -1;
+	else if (cotime1->month > cotime2->month)
+		retval = 1;
+
+	else if (cotime1->day < cotime2->day)
+		retval = -1;
+	else if (cotime1->day > cotime2->day)
+		retval = 1;
+
+	else if (cotime1->hour < cotime2->hour)
+		retval = -1;
+	else if (cotime1->hour > cotime2->hour)
+		retval = 1;
+
+	else if (cotime1->minute < cotime2->minute)
+		retval = -1;
+	else if (cotime1->minute > cotime2->minute)
+		retval = 1;
+
+	else if (cotime1->second < cotime2->second)
+		retval = -1;
+	else if (cotime1->second > cotime2->second)
+		retval = 1;
+
+	else
+		retval = 0;
+
+#if 0
+	g_print ("%s - ", cal_obj_time_to_string (cotime1));
+	g_print ("%s : %i\n", cal_obj_time_to_string (cotime2), retval);
+#endif
+
+	return retval;
+}
+
+
+static gint
+cal_obj_date_only_compare_func (const void *arg1,
+				const void *arg2)
+{
+	CalObjTime *cotime1, *cotime2;
+
+	cotime1 = (CalObjTime*) arg1;
+	cotime2 = (CalObjTime*) arg2;
+
+	if (cotime1->year < cotime2->year)
+		return -1;
+	if (cotime1->year > cotime2->year)
+		return 1;
+
+	if (cotime1->month < cotime2->month)
+		return -1;
+	if (cotime1->month > cotime2->month)
+		return 1;
+
+	if (cotime1->day < cotime2->day)
+		return -1;
+	if (cotime1->day > cotime2->day)
+		return 1;
+
+	return 0;
+}
+
+
+/* Returns the weekday of the given CalObjTime, from 0 (Mon) - 6 (Sun). */
+static gint
+cal_obj_time_weekday		(CalObjTime *cotime)
+{
+	GDate date;
+	gint weekday;
+
+	g_date_clear (&date, 1);
+	g_date_set_dmy (&date, cotime->day, cotime->month + 1, cotime->year);
+
+	/* This results in a value of 0 (Monday) - 6 (Sunday). */
+	weekday = g_date_get_weekday (&date) - 1;
+
+	return weekday;
+}
+
+
+/* Returns the weekday of the given CalObjTime, from 0 - 6. The week start
+   day is Monday by default, but can be set in the recurrence rule. */
+static gint
+cal_obj_time_weekday_offset	(CalObjTime *cotime,
+				 ECalRecurrence *recur)
+{
+	GDate date;
+	gint weekday, offset;
+
+	g_date_clear (&date, 1);
+	g_date_set_dmy (&date, cotime->day, cotime->month + 1, cotime->year);
+
+	/* This results in a value of 0 (Monday) - 6 (Sunday). */
+	weekday = g_date_get_weekday (&date) - 1;
+
+	/* This calculates the offset of our day from the start of the week.
+	   We just add on a week (to avoid any possible negative values) and
+	   then subtract the specified week start day, then convert it into a
+	   value from 0-6. */
+	offset = (weekday + 7 - recur->week_start_day) % 7;
+
+	return offset;
+}
+
+
+/* Returns the day of the year of the given CalObjTime, from 1 - 366. */
+static gint
+cal_obj_time_day_of_year		(CalObjTime *cotime)
+{
+	GDate date;
+
+	g_date_clear (&date, 1);
+	g_date_set_dmy (&date, cotime->day, cotime->month + 1, cotime->year);
+
+	return g_date_get_day_of_year (&date);
+}
+
+
+/* Finds the first week in the given CalObjTime's year, using the same weekday
+   as the event start day (i.e. from the RecurData).
+   The first week of the year is the first week starting from the specified
+   week start day that has 4 days in the new year. It may be in the previous
+   year. */
+static void
+cal_obj_time_find_first_week	(CalObjTime *cotime,
+				 RecurData  *recur_data)
+{
+	GDate date;
+	gint weekday, week_start_day, first_full_week_start_offset, offset;
+
+	/* Find out the weekday of the 1st of the year, 0 (Mon) - 6 (Sun). */
+	g_date_clear (&date, 1);
+	g_date_set_dmy (&date, 1, 1, cotime->year);
+	weekday = g_date_get_weekday (&date) - 1;
+
+	/* Calculate the first day of the year that starts a new week, i.e. the
+	   first week_start_day after weekday, using 0 = 1st Jan.
+	   e.g. if the 1st Jan is a Tuesday (1) and week_start_day is a
+	   Monday (0), the result will be (0 + 7 - 1) % 7 = 6 (7th Jan). */
+	week_start_day = recur_data->recur->week_start_day;
+	first_full_week_start_offset = (week_start_day + 7 - weekday) % 7;
+
+	/* Now see if we have to move backwards 1 week, i.e. if the week
+	   starts on or after Jan 5th (since the previous week has 4 days in
+	   this year and so will be the first week of the year). */
+	if (first_full_week_start_offset >= 4)
+		first_full_week_start_offset -= 7;
+
+	/* Now add the days to get to the event's weekday. */
+	offset = first_full_week_start_offset + recur_data->weekday_offset;
+
+	/* Now move the cotime to the appropriate day. */
+	cotime->month = 0;
+	cotime->day = 1;
+	cal_obj_time_add_days (cotime, offset);
+}
+
+
+static void
+cal_object_time_from_time	(CalObjTime	*cotime,
+				 time_t		 t,
+				 icaltimezone	*zone)
+{
+	struct icaltimetype tt;
+
+	if (zone)
+		tt = icaltime_from_timet_with_zone (t, FALSE, zone);
+	else
+		tt = icaltime_from_timet (t, FALSE);
+
+	cotime->year     = tt.year;
+	cotime->month    = tt.month - 1;
+	cotime->day      = tt.day;
+	cotime->hour     = tt.hour;
+	cotime->minute   = tt.minute;
+	cotime->second   = tt.second;
+	cotime->flags    = FALSE;
+}
+
+
+/* Debugging function to convert a CalObjTime to a string. It uses a static
+   buffer so beware. */
+#ifdef CAL_OBJ_DEBUG
+static char*
+cal_obj_time_to_string		(CalObjTime	*cotime)
+{
+	static char buffer[20];
+	char *weekdays[] = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun",
+			     "   " };
+	gint weekday;
+
+	weekday = cal_obj_time_weekday (cotime);
+
+	sprintf (buffer, "%s %02i/%02i/%04i %02i:%02i:%02i",
+		 weekdays[weekday],
+		 cotime->day, cotime->month + 1, cotime->year,
+		 cotime->hour, cotime->minute, cotime->second);
+	return buffer;
+}
+#endif
+
+
+/* This recalculates the end dates for recurrence & exception rules which use
+   the COUNT property. If refresh is TRUE it will recalculate all enddates
+   for rules which use COUNT. If refresh is FALSE, it will only calculate
+   the enddate if it hasn't already been set. It returns TRUE if the component
+   was changed, i.e. if the component should be saved at some point.
+   We store the enddate in the "X-EVOLUTION-ENDDATE" parameter of the RRULE
+   or EXRULE. */
+static gboolean
+e_cal_recur_ensure_end_dates (ECalComponent	*comp,
+			    gboolean		 refresh,
+			    ECalRecurResolveTimezoneFn  tz_cb,
+			    gpointer		 tz_cb_data)
+{
+	GSList *rrules, *exrules, *elem;
+	gboolean changed = FALSE;
+
+	/* Do the RRULEs. */
+	e_cal_component_get_rrule_property_list (comp, &rrules);
+	for (elem = rrules; elem; elem = elem->next) {
+		changed |= e_cal_recur_ensure_rule_end_date (comp, elem->data,
+							   FALSE, refresh,
+							   tz_cb, tz_cb_data);
+	}
+
+	/* Do the EXRULEs. */
+	e_cal_component_get_exrule_property_list (comp, &exrules);
+	for (elem = exrules; elem; elem = elem->next) {
+		changed |= e_cal_recur_ensure_rule_end_date (comp, elem->data,
+							   TRUE, refresh,
+							   tz_cb, tz_cb_data);
+	}
+
+	return changed;
+}
+
+
+typedef struct _ECalRecurEnsureEndDateData ECalRecurEnsureEndDateData;
+struct _ECalRecurEnsureEndDateData {
+	gint count;
+	gint instances;
+	time_t end_date;
+};
+
+
+static gboolean
+e_cal_recur_ensure_rule_end_date (ECalComponent			*comp,
+				icalproperty			*prop,
+				gboolean			 exception,
+				gboolean			 refresh,
+				ECalRecurResolveTimezoneFn	 tz_cb,
+				gpointer			 tz_cb_data)
+{
+	struct icalrecurrencetype rule;
+	ECalRecurEnsureEndDateData cb_data;
+
+	if (exception)
+		rule = icalproperty_get_exrule (prop);
+	else
+		rule = icalproperty_get_rrule (prop);
+
+	/* If the rule doesn't use COUNT just return. */
+	if (rule.count == 0)
+		return FALSE;
+
+	/* If refresh is FALSE, we check if the enddate is already set, and
+	   if it is we just return. */
+	if (!refresh) {
+		if (e_cal_recur_get_rule_end_date (prop, NULL) != -1)
+			return FALSE;
+	}
+
+	/* Calculate the end date. Note that we initialize end_date to 0, so
+	   if the RULE doesn't generate COUNT instances we save a time_t of 0.
+	   Also note that we use the UTC timezone as the default timezone.
+	   In get_end_date() if the DTSTART is a DATE or floating time, we will
+	   convert the ENDDATE to the current timezone. */
+	cb_data.count = rule.count;
+	cb_data.instances = 0;
+	cb_data.end_date = 0;
+	e_cal_recur_generate_instances_of_rule (comp, prop, -1, -1,
+					      e_cal_recur_ensure_rule_end_date_cb,
+					      &cb_data, tz_cb, tz_cb_data,
+					      icaltimezone_get_utc_timezone ());
+
+	/* Store the end date in the "X-EVOLUTION-ENDDATE" parameter of the
+	   rule. */
+	e_cal_recur_set_rule_end_date (prop, cb_data.end_date);
+		
+	return TRUE;
+}
+
+
+static gboolean
+e_cal_recur_ensure_rule_end_date_cb	(ECalComponent	*comp,
+					 time_t		 instance_start,
+					 time_t		 instance_end,
+					 gpointer	 data)
+{
+	ECalRecurEnsureEndDateData *cb_data;
+
+	cb_data = (ECalRecurEnsureEndDateData*) data;
+
+	cb_data->instances++;
+
+	if (cb_data->instances == cb_data->count) {
+		cb_data->end_date = instance_start;
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+
+/* If default_timezone is set, the saved ENDDATE parameter is assumed to be
+   in that timezone. This is used when the DTSTART is a DATE or floating
+   value, since the RRULE end date will change depending on the timezone that
+   it is evaluated in. */
+static time_t
+e_cal_recur_get_rule_end_date	(icalproperty	*prop,
+				 icaltimezone	*default_timezone)
+{
+	icalparameter *param;
+	const char *xname, *xvalue;
+	icalvalue *value;
+	struct icaltimetype icaltime;
+	icaltimezone *zone;
+
+	param = icalproperty_get_first_parameter (prop, ICAL_X_PARAMETER);
+	while (param) {
+		xname = icalparameter_get_xname (param);
+		if (xname && !strcmp (xname, EVOLUTION_END_DATE_PARAMETER)) {
+			xvalue = icalparameter_get_x (param);
+			value = icalvalue_new_from_string (ICAL_DATETIME_VALUE,
+							   xvalue);
+			if (value) {
+				icaltime = icalvalue_get_datetime (value);
+				icalvalue_free (value);
+
+				zone = default_timezone ? default_timezone : 
+					icaltimezone_get_utc_timezone ();
+				return icaltime_as_timet_with_zone (icaltime,
+								    zone);
+			}
+		}
+
+		param = icalproperty_get_next_parameter (prop,
+							 ICAL_X_PARAMETER);
+	}
+
+	return -1;
+}
+
+
+static void
+e_cal_recur_set_rule_end_date	(icalproperty	*prop,
+				 time_t		 end_date)
+{
+	icalparameter *param;
+	icalvalue *value;
+	icaltimezone *utc_zone;
+	struct icaltimetype icaltime;
+	const char *end_date_string, *xname;
+
+	/* We save the value as a UTC DATE-TIME. */
+	utc_zone = icaltimezone_get_utc_timezone ();
+	icaltime = icaltime_from_timet_with_zone (end_date, FALSE, utc_zone);
+	value = icalvalue_new_datetime (icaltime);
+	end_date_string = icalvalue_as_ical_string (value);
+	icalvalue_free (value);
+
+	/* If we already have an X-EVOLUTION-ENDDATE parameter, set the value
+	   to the new date-time. */
+	param = icalproperty_get_first_parameter (prop, ICAL_X_PARAMETER);
+	while (param) {
+		xname = icalparameter_get_xname (param);
+		if (xname && !strcmp (xname, EVOLUTION_END_DATE_PARAMETER)) {
+			icalparameter_set_x (param, end_date_string);
+			return;
+		}
+		param = icalproperty_get_next_parameter (prop, ICAL_X_PARAMETER);
+	}
+
+	/* Create a new X-EVOLUTION-ENDDATE and add it to the property. */
+	param = icalparameter_new_x (end_date_string);
+	icalparameter_set_xname (param, EVOLUTION_END_DATE_PARAMETER);
+	icalproperty_add_parameter (prop, param);
+}
+
+#ifdef G_OS_WIN32
+#undef e_cal_recur_nth
+static
+#endif
+const const char *e_cal_recur_nth[31] = {
+	N_("1st"),
+	N_("2nd"),
+	N_("3rd"),
+	N_("4th"),
+	N_("5th"),
+	N_("6th"),
+	N_("7th"),
+	N_("8th"),
+	N_("9th"),
+	N_("10th"),
+	N_("11th"),
+	N_("12th"),
+	N_("13th"),
+	N_("14th"),
+	N_("15th"),
+	N_("16th"),
+	N_("17th"),
+	N_("18th"),
+	N_("19th"),
+	N_("20th"),
+	N_("21st"),
+	N_("22nd"),
+	N_("23rd"),
+	N_("24th"),
+	N_("25th"),
+	N_("26th"),
+	N_("27th"),
+	N_("28th"),
+	N_("29th"),
+	N_("30th"),
+	N_("31st")
+};
+
+#ifdef G_OS_WIN32
+
+const char **
+e_cal_get_recur_nth (void)
+{
+	return e_cal_recur_nth;
+}
+
+#endif
Index: calendar/libecal-dbus/e-cal-util.h
===================================================================
--- calendar/libecal-dbus/e-cal-util.h	(revision 409)
+++ calendar/libecal-dbus/e-cal-util.h	(working copy)
@@ -1 +1,143 @@
-link ../libecal/e-cal-util.h
\ No newline at end of file
+/* Evolution calendar utilities and types
+ *
+ * Copyright (C) 2000 Ximian, Inc.
+ * Copyright (C) 2000 Ximian, Inc.
+ *
+ * Author: Federico Mena-Quintero <federico@ximian.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef E_CAL_UTIL_H
+#define E_CAL_UTIL_H
+
+#include <libical/ical.h>
+#include <time.h>
+#include <glib.h>
+#include <libecal/e-cal-component.h>
+#include <libecal/e-cal-recur.h>
+
+G_BEGIN_DECLS
+
+
+
+/* Instance of a calendar object.  This can be an actual occurrence, a
+ * recurrence, or an alarm trigger of a `real' calendar object.
+ */
+typedef struct {
+	char *uid;			/* UID of the object */
+	time_t start;			/* Start time of instance */
+	time_t end;			/* End time of instance */
+} CalObjInstance;
+
+void cal_obj_instance_list_free (GList *list);
+
+/* Used for modifying objects */
+typedef enum {
+	CALOBJ_MOD_THIS          = 1 << 0,
+	CALOBJ_MOD_THISANDPRIOR  = 1 << 1,
+	CALOBJ_MOD_THISANDFUTURE = 1 << 2,
+	CALOBJ_MOD_ALL           = 0x07
+} CalObjModType;
+
+/* Used for mode stuff */
+typedef enum {
+	CAL_MODE_INVALID = -1,
+	CAL_MODE_LOCAL   = 1 << 0,
+	CAL_MODE_REMOTE  = 1 << 1,
+	CAL_MODE_ANY     = 0x07
+} CalMode;
+
+#define cal_mode_to_corba(mode) \
+	(mode == CAL_MODE_LOCAL   ? GNOME_Evolution_Calendar_MODE_LOCAL  : \
+	 mode == CAL_MODE_REMOTE  ? GNOME_Evolution_Calendar_MODE_REMOTE : \
+	 GNOME_Evolution_Calendar_MODE_ANY)
+
+void cal_obj_uid_list_free (GList *list);
+
+icalcomponent *e_cal_util_new_top_level (void);
+icalcomponent *e_cal_util_new_component (icalcomponent_kind kind);
+
+icalcomponent *e_cal_util_parse_ics_string (const char *string);
+icalcomponent *e_cal_util_parse_ics_file (const char *filename);
+
+ECalComponentAlarms *e_cal_util_generate_alarms_for_comp (ECalComponent *comp,
+						       time_t start,
+						       time_t end,
+						       ECalComponentAlarmAction *omit,
+						       ECalRecurResolveTimezoneFn resolve_tzid,
+						       gpointer user_data,
+						       icaltimezone *default_timezone);
+int e_cal_util_generate_alarms_for_list (GList *comps,
+				       time_t start,
+				       time_t end,
+				       ECalComponentAlarmAction *omit,
+				       GSList **comp_alarms,
+				       ECalRecurResolveTimezoneFn resolve_tzid,
+				       gpointer user_data,
+				       icaltimezone *default_timezone);
+
+icaltimezone *e_cal_util_resolve_tzid (const char *tzid, gpointer data);
+
+char *e_cal_util_priority_to_string (int priority);
+int e_cal_util_priority_from_string (const char *string);
+
+void e_cal_util_add_timezones_from_component (icalcomponent *vcal_comp,
+					    icalcomponent *icalcomp);
+
+gboolean e_cal_util_component_is_instance (icalcomponent *icalcomp);
+gboolean e_cal_util_component_has_alarms (icalcomponent *icalcomp);
+gboolean e_cal_util_component_has_organizer (icalcomponent *icalcomp);
+gboolean e_cal_util_component_has_recurrences (icalcomponent *icalcomp);
+gboolean e_cal_util_component_has_rdates (icalcomponent *icalcomp);
+gboolean e_cal_util_component_has_rrules (icalcomponent *icalcomp);
+gboolean e_cal_util_component_has_attendee (icalcomponent *icalcomp);
+gboolean e_cal_util_event_dates_match (icalcomponent *icalcomp1, icalcomponent *icalcomp2);
+/* The static capabilities to be supported by backends */
+#define CAL_STATIC_CAPABILITY_NO_ALARM_REPEAT             "no-alarm-repeat"
+#define CAL_STATIC_CAPABILITY_NO_AUDIO_ALARMS             "no-audio-alarms"
+#define CAL_STATIC_CAPABILITY_NO_DISPLAY_ALARMS           "no-display-alarms"
+#define CAL_STATIC_CAPABILITY_NO_EMAIL_ALARMS             "no-email-alarms"
+#define CAL_STATIC_CAPABILITY_NO_PROCEDURE_ALARMS         "no-procedure-alarms"
+#define CAL_STATIC_CAPABILITY_NO_TASK_ASSIGNMENT          "no-task-assignment"
+#define CAL_STATIC_CAPABILITY_NO_THISANDFUTURE            "no-thisandfuture"
+#define CAL_STATIC_CAPABILITY_NO_THISANDPRIOR             "no-thisandprior"
+#define CAL_STATIC_CAPABILITY_NO_TRANSPARENCY             "no-transparency"
+#define CAL_STATIC_CAPABILITY_ONE_ALARM_ONLY              "one-alarm-only"
+#define CAL_STATIC_CAPABILITY_ORGANIZER_MUST_ATTEND       "organizer-must-attend"
+#define CAL_STATIC_CAPABILITY_ORGANIZER_NOT_EMAIL_ADDRESS "organizer-not-email-address"
+#define CAL_STATIC_CAPABILITY_REMOVE_ALARMS               "remove-alarms"
+#define CAL_STATIC_CAPABILITY_SAVE_SCHEDULES              "save-schedules"
+#define CAL_STATIC_CAPABILITY_NO_CONV_TO_ASSIGN_TASK	  "no-conv-to-assign-task"
+#define CAL_STATIC_CAPABILITY_NO_CONV_TO_RECUR		  "no-conv-to-recur"
+#define CAL_STATIC_CAPABILITY_NO_GEN_OPTIONS		  "no-general-options"
+#define CAL_STATIC_CAPABILITY_REQ_SEND_OPTIONS		  "require-send-options"
+#define CAL_STATIC_CAPABILITY_RECURRENCES_NO_MASTER       "recurrences-no-master-object"
+#define CAL_STATIC_CAPABILITY_ORGANIZER_MUST_ACCEPT      "organizer-must-accept"
+#define CAL_STATIC_CAPABILITY_DELEGATE_SUPPORTED	 "delegate-support"
+#define CAL_STATIC_CAPABILITY_NO_ORGANIZER		 "no-organizer"
+#define CAL_STATIC_CAPABILITY_DELEGATE_TO_MANY		 "delegate-to-many"
+#define CAL_STATIC_CAPABILITY_HAS_UNACCEPTED_MEETING     "has-unaccepted-meeting"
+
+/* Recurrent events. Management for instances */
+icalcomponent *e_cal_util_construct_instance (icalcomponent *icalcomp,
+					    struct icaltimetype rid);
+void           e_cal_util_remove_instances (icalcomponent *icalcomp,
+					  struct icaltimetype rid,
+					  CalObjModType mod);
+
+G_END_DECLS
+
+#endif
+
Index: calendar/libecal-dbus/e-cal-recur.h
===================================================================
--- calendar/libecal-dbus/e-cal-recur.h	(revision 409)
+++ calendar/libecal-dbus/e-cal-recur.h	(working copy)
@@ -1 +1,58 @@
-link ../libecal/e-cal-recur.h
\ No newline at end of file
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Evolution calendar recurrence rule functions
+ *
+ * Copyright (C) 2000 Ximian, Inc.
+ *
+ * Author: Damon Chaplin <damon@ximian.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef E_CAL_RECUR_H
+#define E_CAL_RECUR_H
+
+#include <glib.h>
+#include <libecal/e-cal-component.h>
+
+G_BEGIN_DECLS
+
+typedef gboolean (* ECalRecurInstanceFn) (ECalComponent *comp,
+					 time_t        instance_start,
+					 time_t        instance_end,
+					 gpointer      data);
+
+typedef icaltimezone* (* ECalRecurResolveTimezoneFn)	(const char   *tzid,
+							 gpointer      data);
+
+void	e_cal_recur_generate_instances	(ECalComponent		*comp,
+					 time_t			 start,
+					 time_t			 end,
+					 ECalRecurInstanceFn	 cb,
+					 gpointer                cb_data,
+					 ECalRecurResolveTimezoneFn tz_cb,
+					 gpointer		   tz_cb_data,
+					 icaltimezone		*default_timezone);
+
+/* Localized nth-day-of-month strings. (Use with _() ) */
+#ifdef G_OS_WIN32
+extern const char **e_cal_get_recur_nth (void);
+#define e_cal_recur_nth (e_cal_get_recur_nth ())
+#else
+extern const char *e_cal_recur_nth[31];
+#endif
+
+G_END_DECLS
+
+#endif
Index: calendar/libecal-dbus/e-cal-component.c
===================================================================
--- calendar/libecal-dbus/e-cal-component.c	(revision 409)
+++ calendar/libecal-dbus/e-cal-component.c	(working copy)
@@ -1 +1,5925 @@
-link ../libecal/e-cal-component.c
\ No newline at end of file
+/* Evolution calendar - iCalendar component object
+ *
+ * Copyright (C) 2000 Ximian, Inc.
+ * Copyright (C) 2000 Ximian, Inc.
+ *
+ * Author: Federico Mena-Quintero <federico@ximian.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include "e-cal-component.h"
+#include "e-cal-time-util.h"
+
+
+
+#ifdef G_OS_WIN32
+#define getgid() 0
+#define getppid() 0
+#endif
+
+/* Extension property for alarm components so that we can reference them by UID */
+#define EVOLUTION_ALARM_UID_PROPERTY "X-EVOLUTION-ALARM-UID"
+
+
+struct attendee {
+	icalproperty *prop;
+	icalparameter *cutype_param;
+	icalparameter *member_param;
+	icalparameter *role_param;
+	icalparameter *partstat_param;
+	icalparameter *rsvp_param;
+	icalparameter *delto_param;
+	icalparameter *delfrom_param;
+	icalparameter *sentby_param;
+	icalparameter *cn_param;
+	icalparameter *language_param;
+};
+
+struct attachment {
+	icalproperty *prop;
+	icalattach *attach;
+};
+
+struct text {
+	icalproperty *prop;
+	icalparameter *altrep_param;
+};
+
+struct datetime {
+	icalproperty *prop;
+	icalparameter *tzid_param;
+};
+
+struct organizer {
+	icalproperty *prop;
+	icalparameter *sentby_param;
+	icalparameter *cn_param;
+	icalparameter *language_param;
+};
+
+struct period {
+	icalproperty *prop;
+	icalparameter *value_param;
+};
+
+struct recur_id {
+	struct datetime recur_time;
+	
+	icalparameter *range_param;
+};
+
+/* Private part of the CalComponent structure */
+struct _ECalComponentPrivate {
+	/* The icalcomponent we wrap */
+	icalcomponent *icalcomp;
+
+	/* Properties */
+
+	icalproperty *uid;
+
+	icalproperty *status;
+	GSList *attendee_list;
+	
+	icalproperty *categories;
+
+	icalproperty *classification;
+
+	GSList *comment_list; /* list of struct text */
+
+	icalproperty *completed;
+
+	GSList *contact_list; /* list of struct text */
+
+	icalproperty *created;
+
+	GSList *description_list; /* list of struct text */
+
+	struct datetime dtstart;
+	struct datetime dtend;
+
+	icalproperty *dtstamp;
+
+	/* The DURATION property can be used instead of the VEVENT DTEND or
+	   the VTODO DUE dates. We do not use it directly ourselves, but we
+	   must be able to handle it from incoming data. If a DTEND or DUE
+	   is requested, we convert the DURATION if necessary. If DTEND or
+	   DUE is set, we remove any DURATION. */
+	icalproperty *duration;
+
+	struct datetime due;
+
+	GSList *exdate_list; /* list of struct datetime */
+	GSList *exrule_list; /* list of icalproperty objects */
+
+	struct organizer organizer;
+	
+	icalproperty *geo;
+	icalproperty *last_modified;
+	icalproperty *percent;
+	icalproperty *priority;
+
+	struct recur_id recur_id;
+	
+	GSList *rdate_list; /* list of struct period */
+
+	GSList *rrule_list; /* list of icalproperty objects */
+
+	icalproperty *sequence;
+
+	struct {
+		icalproperty *prop;
+		icalparameter *altrep_param;
+	} summary;
+
+	icalproperty *transparency;
+	icalproperty *url;
+	icalproperty *location;
+
+	GSList *attachment_list;
+
+	/* Subcomponents */
+
+	GHashTable *alarm_uid_hash;
+
+	/* Whether we should increment the sequence number when piping the
+	 * object over the wire.
+	 */
+	guint need_sequence_inc : 1;
+};
+
+/* Private structure for alarms */
+struct _ECalComponentAlarm {
+	/* Alarm icalcomponent we wrap */
+	icalcomponent *icalcomp;
+
+	/* Our extension UID property */
+	icalproperty *uid;
+
+	/* Properties */
+
+	icalproperty *action;
+	icalproperty *attach; /* FIXME: see scan_alarm_property() below */
+
+	struct {
+		icalproperty *prop;
+		icalparameter *altrep_param;
+	} description;
+
+	icalproperty *duration;
+	icalproperty *repeat;
+	icalproperty *trigger;
+
+	GSList *attendee_list;
+};
+
+
+
+static void e_cal_component_class_init (ECalComponentClass *klass);
+static void e_cal_component_init (ECalComponent *comp, ECalComponentClass *klass);
+static void e_cal_component_finalize (GObject *object);
+
+static GObjectClass *parent_class;
+
+
+
+/**
+ * e_cal_component_get_type:
+ *
+ * Registers the #ECalComponent class if necessary, and returns the type ID
+ * associated to it.
+ *
+ * Return value: The type ID of the #ECalComponent class.
+ **/
+GType
+e_cal_component_get_type (void)
+{
+	static GType e_cal_component_type = 0;
+
+	if (!e_cal_component_type) {
+		static GTypeInfo info = {
+                        sizeof (ECalComponentClass),
+                        (GBaseInitFunc) NULL,
+                        (GBaseFinalizeFunc) NULL,
+                        (GClassInitFunc) e_cal_component_class_init,
+                        NULL, NULL,
+                        sizeof (ECalComponent),
+                        0,
+                        (GInstanceInitFunc) e_cal_component_init
+                };
+		e_cal_component_type = g_type_register_static (G_TYPE_OBJECT, "ECalComponent", &info, 0);
+	}
+
+	return e_cal_component_type;
+}
+
+/* Class initialization function for the calendar component object */
+static void
+e_cal_component_class_init (ECalComponentClass *klass)
+{
+	GObjectClass *object_class;
+
+	object_class = (GObjectClass *) klass;
+
+	parent_class = g_type_class_peek_parent (klass);
+
+	object_class->finalize = e_cal_component_finalize;
+}
+
+/* Object initialization function for the calendar component object */
+static void
+e_cal_component_init (ECalComponent *comp, ECalComponentClass *klass)
+{
+	ECalComponentPrivate *priv;
+
+	priv = g_new0 (ECalComponentPrivate, 1);
+	comp->priv = priv;
+
+	priv->alarm_uid_hash = g_hash_table_new (g_str_hash, g_str_equal);
+}
+
+/* Does a simple g_free() of the elements of a GSList and then frees the list
+ * itself.  Returns NULL.
+ */
+static GSList *
+free_slist (GSList *slist)
+{
+	GSList *l;
+
+	for (l = slist; l; l = l->next)
+		g_free (l->data);
+
+	g_slist_free (slist);
+	return NULL;
+}
+
+/* Used from g_hash_table_foreach_remove() to free the alarm UIDs hash table.
+ * We do not need to do anything to individual elements since we were storing
+ * the UID pointers inside the icalproperties themselves.
+ */
+static gboolean
+free_alarm_cb (gpointer key, gpointer value, gpointer data)
+{
+	return TRUE;
+}
+
+/* Frees the internal icalcomponent only if it does not have a parent.  If it
+ * does, it means we don't own it and we shouldn't free it.
+ */
+static void
+free_icalcomponent (ECalComponent *comp, gboolean free)
+{
+	ECalComponentPrivate *priv;
+	GSList *l;
+	
+	priv = comp->priv;
+
+	if (!priv->icalcomp)
+		return;
+
+	/* Free the icalcomponent */
+
+	if (free && icalcomponent_get_parent (priv->icalcomp) == NULL) {
+		icalcomponent_free (priv->icalcomp);
+		priv->icalcomp = NULL;
+	}
+	
+	/* Free the mappings */
+
+	priv->uid = NULL;
+
+	priv->status = NULL;
+
+	for (l = priv->attachment_list; l != NULL; l = l->next)
+		g_free (l->data);
+	g_slist_free (priv->attachment_list);
+	priv->attachment_list = NULL;
+
+	for (l = priv->attendee_list; l != NULL; l = l->next)
+		g_free (l->data);
+	g_slist_free (priv->attendee_list);
+	priv->attendee_list = NULL;
+	
+	priv->categories = NULL;
+
+	priv->classification = NULL;
+	priv->comment_list = NULL;
+	priv->completed = NULL;
+	priv->contact_list = NULL;
+	priv->created = NULL;
+
+	priv->description_list = free_slist (priv->description_list);
+
+	priv->dtend.prop = NULL;
+	priv->dtend.tzid_param = NULL;
+
+	priv->dtstamp = NULL;
+
+	priv->dtstart.prop = NULL;
+	priv->dtstart.tzid_param = NULL;
+
+	priv->due.prop = NULL;
+	priv->due.tzid_param = NULL;
+
+	priv->duration = NULL;
+
+	priv->exdate_list = free_slist (priv->exdate_list);
+
+	g_slist_free (priv->exrule_list);
+	priv->exrule_list = NULL;
+
+	priv->geo = NULL;
+	priv->last_modified = NULL;
+	priv->percent = NULL;
+	priv->priority = NULL;
+
+	priv->rdate_list = free_slist (priv->rdate_list);
+
+	g_slist_free (priv->rrule_list);
+	priv->rrule_list = NULL;
+
+	priv->sequence = NULL;
+
+	priv->summary.prop = NULL;
+	priv->summary.altrep_param = NULL;
+
+	priv->transparency = NULL;
+	priv->url = NULL;
+	priv->location = NULL;
+
+	/* Free the subcomponents */
+
+	g_hash_table_foreach_remove (priv->alarm_uid_hash, free_alarm_cb, NULL);
+
+	/* Clean up */
+
+	priv->need_sequence_inc = FALSE;
+}
+
+/* Finalize handler for the calendar component object */
+static void
+e_cal_component_finalize (GObject *object)
+{
+	ECalComponent *comp;
+	ECalComponentPrivate *priv;
+
+	g_return_if_fail (object != NULL);
+	g_return_if_fail (E_IS_CAL_COMPONENT (object));
+
+	comp = E_CAL_COMPONENT (object);
+	priv = comp->priv;
+
+	free_icalcomponent (comp, TRUE);
+	g_hash_table_destroy (priv->alarm_uid_hash);
+	priv->alarm_uid_hash = NULL;
+
+	g_free (priv);
+	comp->priv = NULL;
+
+	if (G_OBJECT_CLASS (parent_class)->finalize)
+		(* G_OBJECT_CLASS (parent_class)->finalize) (object);
+}
+
+
+
+/**
+ * e_cal_component_gen_uid:
+ *
+ * Generates a unique identifier suitable for calendar components.
+ *
+ * Return value: A unique identifier string.  Every time this function is called
+ * a different string is returned.
+ **/
+char *
+e_cal_component_gen_uid (void)
+{
+        char *iso, *ret;
+	static char *hostname;
+	time_t t = time (NULL);
+	static int serial;
+
+	if (!hostname) {
+#ifndef G_OS_WIN32
+		static char buffer [512];
+
+		if ((gethostname (buffer, sizeof (buffer) - 1) == 0) &&
+		    (buffer [0] != 0))
+			hostname = buffer;
+		else
+			hostname = "localhost";
+#else
+		hostname = g_get_host_name ();
+#endif
+	}
+
+	iso = isodate_from_time_t (t);
+	ret = g_strdup_printf ("%s-%d-%d-%d-%d@%s",
+			       iso,
+			       getpid (),
+			       getgid (),
+			       getppid (),
+			       serial++,
+			       hostname);
+	g_free (iso);
+
+	return ret;
+}
+
+/**
+ * e_cal_component_new:
+ *
+ * Creates a new empty calendar component object.  Once created, you should set it from an
+ * existing #icalcomponent structure by using e_cal_component_set_icalcomponent() or with a
+ * new empty component type by using e_cal_component_set_new_vtype().
+ *
+ * Return value: A newly-created calendar component object.
+ **/
+ECalComponent *
+e_cal_component_new (void)
+{
+	return E_CAL_COMPONENT (g_object_new (E_TYPE_CAL_COMPONENT, NULL));
+}
+
+/**
+ * e_cal_component_new_from_string:
+ * @calobj: A string representation of an iCalendar component.
+ *
+ * Creates a new calendar component object from the given iCalendar string.
+ *
+ * Return value: A calendar component representing the given iCalendar string on
+ * success, NULL if there was an error.
+ **/
+ECalComponent *
+e_cal_component_new_from_string (const char *calobj)
+{
+	ECalComponent *comp;
+	icalcomponent *icalcomp;
+
+	g_return_val_if_fail (calobj != NULL, NULL);
+
+	icalcomp = icalparser_parse_string (calobj);
+	if (!icalcomp)
+		return NULL;
+
+	comp = e_cal_component_new ();
+	if (!e_cal_component_set_icalcomponent (comp, icalcomp)) {
+		icalcomponent_free (icalcomp);
+		g_object_unref (comp);
+
+		return NULL;
+	}
+
+	return comp;
+}
+
+/**
+ * e_cal_component_clone:
+ * @comp: A calendar component object.
+ *
+ * Creates a new calendar component object by copying the information from
+ * another one.
+ *
+ * Return value: A newly-created calendar component with the same values as the
+ * original one.
+ **/
+ECalComponent *
+e_cal_component_clone (ECalComponent *comp)
+{
+	ECalComponentPrivate *priv;
+	ECalComponent *new_comp;
+	icalcomponent *new_icalcomp;
+
+	g_return_val_if_fail (comp != NULL, NULL);
+	g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), NULL);
+
+	priv = comp->priv;
+	g_return_val_if_fail (priv->need_sequence_inc == FALSE, NULL);
+
+	new_comp = e_cal_component_new ();
+
+	if (priv->icalcomp) {
+		new_icalcomp = icalcomponent_new_clone (priv->icalcomp);
+		e_cal_component_set_icalcomponent (new_comp, new_icalcomp);
+	}
+
+	return new_comp;
+}
+
+/* Scans an attachment property */
+static void
+scan_attachment (GSList **attachment_list, icalproperty *prop) 
+{
+	struct attachment *attachment;
+	
+	attachment = g_new (struct attachment, 1);
+	attachment->prop = prop;
+
+	attachment->attach = icalproperty_get_attach (prop);
+
+	*attachment_list = g_slist_append (*attachment_list, attachment);
+}
+
+/* Scans an attendee property */
+static void
+scan_attendee (GSList **attendee_list, icalproperty *prop) 
+{
+	struct attendee *attendee;
+	
+	attendee = g_new (struct attendee, 1);
+	attendee->prop = prop;
+
+	attendee->cutype_param = icalproperty_get_first_parameter (prop, ICAL_CUTYPE_PARAMETER);
+	attendee->member_param = icalproperty_get_first_parameter (prop, ICAL_MEMBER_PARAMETER);
+	attendee->role_param = icalproperty_get_first_parameter (prop, ICAL_ROLE_PARAMETER);
+	attendee->partstat_param = icalproperty_get_first_parameter (prop, ICAL_PARTSTAT_PARAMETER);
+	attendee->rsvp_param = icalproperty_get_first_parameter (prop, ICAL_RSVP_PARAMETER);
+	attendee->delto_param = icalproperty_get_first_parameter (prop, ICAL_DELEGATEDTO_PARAMETER);
+	attendee->delfrom_param = icalproperty_get_first_parameter (prop, ICAL_DELEGATEDFROM_PARAMETER);
+	attendee->sentby_param = icalproperty_get_first_parameter (prop, ICAL_SENTBY_PARAMETER);
+	attendee->cn_param = icalproperty_get_first_parameter (prop, ICAL_CN_PARAMETER);
+	attendee->language_param = icalproperty_get_first_parameter (prop, ICAL_LANGUAGE_PARAMETER);
+	
+	*attendee_list = g_slist_append (*attendee_list, attendee);
+}
+
+/* Scans a date/time and timezone pair property */
+static void
+scan_datetime (ECalComponent *comp, struct datetime *datetime, icalproperty *prop)
+{
+	ECalComponentPrivate *priv;
+
+	priv = comp->priv;
+
+	datetime->prop = prop;
+	datetime->tzid_param = icalproperty_get_first_parameter (prop, ICAL_TZID_PARAMETER);
+}
+
+/* Scans an exception date property */
+static void
+scan_exdate (ECalComponent *comp, icalproperty *prop)
+{
+	ECalComponentPrivate *priv;
+	struct datetime *dt;
+
+	priv = comp->priv;
+
+	dt = g_new (struct datetime, 1);
+	dt->prop = prop;
+	dt->tzid_param = icalproperty_get_first_parameter (prop, ICAL_TZID_PARAMETER);
+
+	priv->exdate_list = g_slist_append (priv->exdate_list, dt);
+}
+
+/* Scans and attendee property */
+static void
+scan_organizer (ECalComponent *comp, struct organizer *organizer, icalproperty *prop) 
+{
+	organizer->prop = prop;
+
+	organizer->sentby_param = icalproperty_get_first_parameter (prop, ICAL_SENTBY_PARAMETER);
+	organizer->cn_param = icalproperty_get_first_parameter (prop, ICAL_CN_PARAMETER);
+	organizer->language_param = icalproperty_get_first_parameter (prop, ICAL_LANGUAGE_PARAMETER);
+}
+
+/* Scans an icalperiodtype property */
+static void
+scan_period (ECalComponent *comp, GSList **list, icalproperty *prop)
+{
+	struct period *period;
+
+	period = g_new (struct period, 1);
+	period->prop = prop;
+	period->value_param = icalproperty_get_first_parameter (prop, ICAL_VALUE_PARAMETER);
+
+	*list = g_slist_append (*list, period);
+}
+
+
+/* Scans an icalrecurtype property */
+static void
+scan_recur_id (ECalComponent *comp, struct recur_id *recur_id, icalproperty *prop)
+{
+	scan_datetime (comp, &recur_id->recur_time, prop);
+
+	recur_id->range_param = icalproperty_get_first_parameter (prop, ICAL_RANGE_PARAMETER);
+}
+
+/* Scans an icalrecurtype property */
+static void
+scan_recur (ECalComponent *comp, GSList **list, icalproperty *prop)
+{
+	*list = g_slist_append (*list, prop);
+}
+
+/* Scans the summary property */
+static void
+scan_summary (ECalComponent *comp, icalproperty *prop)
+{
+	ECalComponentPrivate *priv;
+
+	priv = comp->priv;
+
+	priv->summary.prop = prop;
+	priv->summary.altrep_param = icalproperty_get_first_parameter (prop, ICAL_ALTREP_PARAMETER);
+}
+
+/* Scans a text (i.e. text + altrep) property */
+static void
+scan_text (ECalComponent *comp, GSList **text_list, icalproperty *prop)
+{
+	struct text *text;
+
+	text = g_new (struct text, 1);
+	text->prop = prop;
+	text->altrep_param = icalproperty_get_first_parameter (prop, ICAL_ALTREP_PARAMETER);
+
+	*text_list = g_slist_append (*text_list, text);
+}
+
+/* Scans an icalproperty and adds its mapping to the component */
+static void
+scan_property (ECalComponent *comp, icalproperty *prop)
+{
+	ECalComponentPrivate *priv;
+	icalproperty_kind kind;
+
+	priv = comp->priv;
+
+	kind = icalproperty_isa (prop);
+
+	switch (kind) {
+	case ICAL_STATUS_PROPERTY:
+		priv->status = prop;
+		break;
+
+	case ICAL_ATTACH_PROPERTY:
+		scan_attachment (&priv->attachment_list, prop);
+		break;
+
+	case ICAL_ATTENDEE_PROPERTY:
+		scan_attendee (&priv->attendee_list, prop);
+		break;
+
+	case ICAL_CATEGORIES_PROPERTY:
+		priv->categories = prop;
+		break;
+
+	case ICAL_CLASS_PROPERTY:
+		priv->classification = prop;
+		break;
+
+	case ICAL_COMMENT_PROPERTY:
+		scan_text (comp, &priv->comment_list, prop);
+		break;
+
+	case ICAL_COMPLETED_PROPERTY:
+		priv->completed = prop;
+		break;
+
+	case ICAL_CONTACT_PROPERTY:
+		scan_text (comp, &priv->contact_list, prop);
+		break;
+
+	case ICAL_CREATED_PROPERTY:
+		priv->created = prop;
+		break;
+
+	case ICAL_DESCRIPTION_PROPERTY:
+		scan_text (comp, &priv->description_list, prop);
+		break;
+
+	case ICAL_DTEND_PROPERTY:
+		scan_datetime (comp, &priv->dtend, prop);
+		break;
+
+	case ICAL_DTSTAMP_PROPERTY:
+		priv->dtstamp = prop;
+		break;
+
+	case ICAL_DTSTART_PROPERTY:
+		scan_datetime (comp, &priv->dtstart, prop);
+		break;
+
+	case ICAL_DUE_PROPERTY:
+		scan_datetime (comp, &priv->due, prop);
+		break;
+
+	case ICAL_DURATION_PROPERTY:
+		priv->duration = prop;
+		break;
+
+	case ICAL_EXDATE_PROPERTY:
+		scan_exdate (comp, prop);
+		break;
+
+	case ICAL_EXRULE_PROPERTY:
+		scan_recur (comp, &priv->exrule_list, prop);
+		break;
+
+	case ICAL_GEO_PROPERTY:
+		priv->geo = prop;
+		break;
+
+	case ICAL_LASTMODIFIED_PROPERTY:
+		priv->last_modified = prop;
+		break;
+
+	case ICAL_ORGANIZER_PROPERTY:
+		scan_organizer (comp, &priv->organizer, prop);
+		break;
+
+	case ICAL_PERCENTCOMPLETE_PROPERTY:
+		priv->percent = prop;
+		break;
+
+	case ICAL_PRIORITY_PROPERTY:
+		priv->priority = prop;
+		break;
+
+	case ICAL_RECURRENCEID_PROPERTY:
+		scan_recur_id (comp, &priv->recur_id, prop);
+		break;
+
+	case ICAL_RDATE_PROPERTY:
+		scan_period (comp, &priv->rdate_list, prop);
+		break;
+
+	case ICAL_RRULE_PROPERTY:
+		scan_recur (comp, &priv->rrule_list, prop);
+		break;
+
+	case ICAL_SEQUENCE_PROPERTY:
+		priv->sequence = prop;
+		break;
+
+	case ICAL_SUMMARY_PROPERTY:
+		scan_summary (comp, prop);
+		break;
+
+	case ICAL_TRANSP_PROPERTY:
+		priv->transparency = prop;
+		break;
+
+	case ICAL_UID_PROPERTY:
+		priv->uid = prop;
+		break;
+
+	case ICAL_URL_PROPERTY:
+		priv->url = prop;
+		break;
+
+	case ICAL_LOCATION_PROPERTY :
+		priv->location = prop;
+		break;
+
+	default:
+		break;
+	}
+}
+
+/* Gets our alarm UID string from a property that is known to contain it */
+static const char *
+alarm_uid_from_prop (icalproperty *prop)
+{
+	const char *xstr;
+
+	g_assert (icalproperty_isa (prop) == ICAL_X_PROPERTY);
+
+	xstr = icalproperty_get_x (prop);
+	g_assert (xstr != NULL);
+
+	return xstr;
+}
+
+/* Sets our alarm UID extension property on an alarm component.  Returns a
+ * pointer to the UID string inside the property itself.
+ */
+static const char *
+set_alarm_uid (icalcomponent *alarm, const char *auid)
+{
+	icalproperty *prop;
+	const char *inprop_auid;
+
+	/* Create the new property */
+
+	prop = icalproperty_new_x ((char *) auid);
+	icalproperty_set_x_name (prop, EVOLUTION_ALARM_UID_PROPERTY);
+
+	icalcomponent_add_property (alarm, prop);
+
+	inprop_auid = alarm_uid_from_prop (prop);
+	return inprop_auid;
+}
+
+/* Removes any alarm UID extension properties from an alarm subcomponent */
+static void
+remove_alarm_uid (icalcomponent *alarm)
+{
+	icalproperty *prop;
+	GSList *list, *l;
+
+	list = NULL;
+
+	for (prop = icalcomponent_get_first_property (alarm, ICAL_X_PROPERTY);
+	     prop;
+	     prop = icalcomponent_get_next_property (alarm, ICAL_X_PROPERTY)) {
+		const char *xname;
+
+		xname = icalproperty_get_x_name (prop);
+		g_assert (xname != NULL);
+
+		if (strcmp (xname, EVOLUTION_ALARM_UID_PROPERTY) == 0)
+			list = g_slist_prepend (list, prop);
+	}
+
+	for (l = list; l; l = l->next) {
+		prop = l->data;
+		icalcomponent_remove_property (alarm, prop);
+		icalproperty_free (prop);
+	}
+
+	g_slist_free (list);
+}
+
+/* Adds an alarm subcomponent to the calendar component's mapping table.  The
+ * actual UID with which it gets added may not be the same as the specified one;
+ * this function will change it if the table already had an alarm subcomponent
+ * with the specified UID.  Returns the actual UID used.
+ */
+static const char *
+add_alarm (ECalComponent *comp, icalcomponent *alarm, const char *auid)
+{
+	ECalComponentPrivate *priv;
+	icalcomponent *old_alarm;
+
+	priv = comp->priv;
+
+	/* First we see if we already have an alarm with the requested UID.  In
+	 * that case, we need to change the new UID to something else.  This
+	 * should never happen, but who knows.
+	 */
+
+	old_alarm = g_hash_table_lookup (priv->alarm_uid_hash, auid);
+	if (old_alarm != NULL) {
+		char *new_auid;
+
+		g_message ("add_alarm(): Got alarm with duplicated UID `%s', changing it...", auid);
+
+		remove_alarm_uid (alarm);
+
+		new_auid = e_cal_component_gen_uid ();
+		auid = set_alarm_uid (alarm, new_auid);
+		g_free (new_auid);
+	}
+
+	g_hash_table_insert (priv->alarm_uid_hash, (char *) auid, alarm);
+	return auid;
+}
+
+/* Scans an alarm subcomponent, adds an UID extension property to it (so that we
+ * can reference alarms by unique IDs), and adds its mapping to the component.  */
+static void
+scan_alarm (ECalComponent *comp, icalcomponent *alarm)
+{
+	ECalComponentPrivate *priv;
+	icalproperty *prop;
+	const char *auid;
+	char *new_auid;
+
+	priv = comp->priv;
+
+	for (prop = icalcomponent_get_first_property (alarm, ICAL_X_PROPERTY);
+	     prop;
+	     prop = icalcomponent_get_next_property (alarm, ICAL_X_PROPERTY)) {
+		const char *xname;
+
+		xname = icalproperty_get_x_name (prop);
+		g_assert (xname != NULL);
+
+		if (strcmp (xname, EVOLUTION_ALARM_UID_PROPERTY) == 0) {
+			auid = alarm_uid_from_prop (prop);
+			add_alarm (comp, alarm, auid);
+			return;
+		}
+	}
+
+	/* The component has no alarm UID property, so we create one. */
+
+	new_auid = e_cal_component_gen_uid ();
+	auid = set_alarm_uid (alarm, new_auid);
+	g_free (new_auid);
+
+	add_alarm (comp, alarm, auid);
+}
+
+/* Scans an icalcomponent for its properties so that we can provide
+ * random-access to them.  It also builds a hash table of the component's alarm
+ * subcomponents.
+ */
+static void
+scan_icalcomponent (ECalComponent *comp)
+{
+	ECalComponentPrivate *priv;
+	icalproperty *prop;
+	icalcompiter iter;
+
+	priv = comp->priv;
+
+	g_assert (priv->icalcomp != NULL);
+
+	/* Scan properties */
+
+	for (prop = icalcomponent_get_first_property (priv->icalcomp, ICAL_ANY_PROPERTY);
+	     prop;
+	     prop = icalcomponent_get_next_property (priv->icalcomp, ICAL_ANY_PROPERTY))
+		scan_property (comp, prop);
+
+	/* Scan subcomponents */
+
+	for (iter = icalcomponent_begin_component (priv->icalcomp, ICAL_VALARM_COMPONENT);
+	     icalcompiter_deref (&iter) != NULL;
+	     icalcompiter_next (&iter)) {
+		icalcomponent *subcomp;
+
+		subcomp = icalcompiter_deref (&iter);
+		scan_alarm (comp, subcomp);
+	}
+}
+
+/* Ensures that the mandatory calendar component properties (uid, dtstamp) do
+ * exist.  If they don't exist, it creates them automatically.
+ */
+static void
+ensure_mandatory_properties (ECalComponent *comp)
+{
+	ECalComponentPrivate *priv;
+
+	priv = comp->priv;
+	g_assert (priv->icalcomp != NULL);
+
+	if (!priv->uid) {
+		char *uid;
+
+		uid = e_cal_component_gen_uid ();
+		priv->uid = icalproperty_new_uid (uid);
+		g_free (uid);
+
+		icalcomponent_add_property (priv->icalcomp, priv->uid);
+	}
+
+	if (!priv->dtstamp) {
+		struct icaltimetype t;
+
+		t = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
+
+		priv->dtstamp = icalproperty_new_dtstamp (t);
+		icalcomponent_add_property (priv->icalcomp, priv->dtstamp);
+	}
+}
+
+/**
+ * e_cal_component_set_new_vtype:
+ * @comp: A calendar component object.
+ * @type: Type of calendar component to create.
+ *
+ * Clears any existing component data from a calendar component object and
+ * creates a new #icalcomponent of the specified type for it.  The only property
+ * that will be set in the new component will be its unique identifier.
+ **/
+void
+e_cal_component_set_new_vtype (ECalComponent *comp, ECalComponentVType type)
+{
+	ECalComponentPrivate *priv;
+	icalcomponent *icalcomp;
+	icalcomponent_kind kind;
+
+	g_return_if_fail (comp != NULL);
+	g_return_if_fail (E_IS_CAL_COMPONENT (comp));
+
+	priv = comp->priv;
+
+	free_icalcomponent (comp, TRUE);
+
+	if (type == E_CAL_COMPONENT_NO_TYPE)
+		return;
+
+	/* Figure out the kind and create the icalcomponent */
+
+	switch (type) {
+	case E_CAL_COMPONENT_EVENT:
+		kind = ICAL_VEVENT_COMPONENT;
+		break;
+
+	case E_CAL_COMPONENT_TODO:
+		kind = ICAL_VTODO_COMPONENT;
+		break;
+
+	case E_CAL_COMPONENT_JOURNAL:
+		kind = ICAL_VJOURNAL_COMPONENT;
+		break;
+
+	case E_CAL_COMPONENT_FREEBUSY:
+		kind = ICAL_VFREEBUSY_COMPONENT;
+		break;
+
+	case E_CAL_COMPONENT_TIMEZONE:
+		kind = ICAL_VTIMEZONE_COMPONENT;
+		break;
+
+	default:
+		g_assert_not_reached ();
+		kind = ICAL_NO_COMPONENT;
+	}
+
+	icalcomp = icalcomponent_new (kind);
+	if (!icalcomp) {
+		g_message ("e_cal_component_set_new_vtype(): Could not create the icalcomponent!");
+		return;
+	}
+
+	/* Scan the component to build our mapping table */
+
+	priv->icalcomp = icalcomp;
+	scan_icalcomponent (comp);
+
+	/* Add missing stuff */
+
+	ensure_mandatory_properties (comp);
+}
+
+/**
+ * e_cal_component_set_icalcomponent:
+ * @comp: A calendar component object.
+ * @icalcomp: An #icalcomponent.
+ *
+ * Sets the contents of a calendar component object from an #icalcomponent
+ * structure.  If the @comp already had an #icalcomponent set into it, it will
+ * will be freed automatically if the #icalcomponent does not have a parent
+ * component itself.
+ *
+ * Supported component types are VEVENT, VTODO, VJOURNAL, VFREEBUSY, and VTIMEZONE.
+ *
+ * Return value: TRUE on success, FALSE if @icalcomp is an unsupported component
+ * type.
+ **/
+gboolean
+e_cal_component_set_icalcomponent (ECalComponent *comp, icalcomponent *icalcomp)
+{
+	ECalComponentPrivate *priv;
+	icalcomponent_kind kind;
+
+	g_return_val_if_fail (comp != NULL, FALSE);
+	g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), FALSE);
+
+	priv = comp->priv;
+
+	if (priv->icalcomp == icalcomp)
+		return TRUE;
+
+	free_icalcomponent (comp, TRUE);
+
+	if (!icalcomp) {
+		priv->icalcomp = NULL;
+		return TRUE;
+	}
+
+	kind = icalcomponent_isa (icalcomp);
+
+	if (!(kind == ICAL_VEVENT_COMPONENT
+	      || kind == ICAL_VTODO_COMPONENT
+	      || kind == ICAL_VJOURNAL_COMPONENT
+	      || kind == ICAL_VFREEBUSY_COMPONENT
+	      || kind == ICAL_VTIMEZONE_COMPONENT))
+		return FALSE;
+
+	priv->icalcomp = icalcomp;
+
+	scan_icalcomponent (comp);
+	ensure_mandatory_properties (comp);
+
+	return TRUE;
+}
+
+/**
+ * e_cal_component_get_icalcomponent:
+ * @comp: A calendar component object.
+ *
+ * Queries the #icalcomponent structure that a calendar component object is
+ * wrapping.
+ *
+ * Return value: An #icalcomponent structure, or NULL if the @comp has no
+ * #icalcomponent set to it.
+ **/
+icalcomponent *
+e_cal_component_get_icalcomponent (ECalComponent *comp)
+{
+	ECalComponentPrivate *priv;
+
+	g_return_val_if_fail (comp != NULL, NULL);
+	g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), NULL);
+
+	priv = comp->priv;
+	g_return_val_if_fail (priv->need_sequence_inc == FALSE, NULL);
+
+	return priv->icalcomp;
+}
+
+/**
+ * e_cal_component_rescan:
+ * @comp: A calendar component object.
+ *
+ * Rescans the #icalcomponent being wrapped by the given calendar component. This
+ * would replace any value that was changed in the wrapped #icalcomponent.
+ */
+void
+e_cal_component_rescan (ECalComponent *comp)
+{
+	ECalComponentPrivate *priv;
+
+	g_return_if_fail (comp != NULL);
+	g_return_if_fail (E_IS_CAL_COMPONENT (comp));
+
+	priv = comp->priv;
+
+	/* Clear everything out */
+	free_icalcomponent (comp, FALSE);
+
+	/* Rescan */
+	scan_icalcomponent (comp);
+	ensure_mandatory_properties (comp);
+}
+
+/**
+ * e_cal_component_strip_errors:
+ * @comp: A calendar component object.
+ *
+ * Strips all error messages from the calendar component. Those error messages are
+ * added to the iCalendar string representation whenever an invalid is used for
+ * one of its fields.
+ */
+void
+e_cal_component_strip_errors (ECalComponent *comp)
+{
+	ECalComponentPrivate *priv;
+	
+	g_return_if_fail (comp != NULL);
+	g_return_if_fail (E_IS_CAL_COMPONENT (comp));
+
+	priv = comp->priv;
+
+	icalcomponent_strip_errors (priv->icalcomp);
+}
+
+/**
+ * e_cal_component_get_vtype:
+ * @comp: A calendar component object.
+ *
+ * Queries the type of a calendar component object.
+ *
+ * Return value: The type of the component, as defined by RFC 2445.
+ **/
+ECalComponentVType
+e_cal_component_get_vtype (ECalComponent *comp)
+{
+	ECalComponentPrivate *priv;
+	icalcomponent_kind kind;
+
+	g_return_val_if_fail (comp != NULL, E_CAL_COMPONENT_NO_TYPE);
+	g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), E_CAL_COMPONENT_NO_TYPE);
+
+	priv = comp->priv;
+	g_return_val_if_fail (priv->icalcomp != NULL, E_CAL_COMPONENT_NO_TYPE);
+
+	kind = icalcomponent_isa (priv->icalcomp);
+	switch (kind) {
+	case ICAL_VEVENT_COMPONENT:
+		return E_CAL_COMPONENT_EVENT;
+
+	case ICAL_VTODO_COMPONENT:
+		return E_CAL_COMPONENT_TODO;
+
+	case ICAL_VJOURNAL_COMPONENT:
+		return E_CAL_COMPONENT_JOURNAL;
+
+	case ICAL_VFREEBUSY_COMPONENT:
+		return E_CAL_COMPONENT_FREEBUSY;
+
+	case ICAL_VTIMEZONE_COMPONENT:
+		return E_CAL_COMPONENT_TIMEZONE;
+
+	default:
+		/* We should have been loaded with a supported type! */
+		g_assert_not_reached ();
+		return E_CAL_COMPONENT_NO_TYPE;
+	}
+}
+
+/**
+ * e_cal_component_get_as_string:
+ * @comp: A calendar component.
+ *
+ * Gets the iCalendar string representation of a calendar component.  You should
+ * call e_cal_component_commit_sequence() before this function to ensure that the
+ * component's sequence number is consistent with the state of the object.
+ *
+ * Return value: String representation of the calendar component according to
+ * RFC 2445.
+ **/
+char *
+e_cal_component_get_as_string (ECalComponent *comp)
+{
+	ECalComponentPrivate *priv;
+	char *str, *buf;
+
+	g_return_val_if_fail (comp != NULL, NULL);
+	g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), NULL);
+
+	priv = comp->priv;
+	g_return_val_if_fail (priv->icalcomp != NULL, NULL);
+
+	/* Ensure that the user has committed the new SEQUENCE */
+	g_return_val_if_fail (priv->need_sequence_inc == FALSE, NULL);
+
+	/* We dup the string; libical owns that memory */
+
+	str = icalcomponent_as_ical_string (priv->icalcomp);
+
+	if (str)
+		buf = g_strdup (str);
+	else
+		buf = NULL;
+
+	return buf;
+}
+
+/* Used from g_hash_table_foreach(); ensures that an alarm subcomponent
+ * has the mandatory properties it needs.
+ */
+static void
+ensure_alarm_properties_cb (gpointer key, gpointer value, gpointer data)
+{
+	ECalComponent *comp;
+	ECalComponentPrivate *priv;
+	icalcomponent *alarm;
+	icalproperty *prop;
+	enum icalproperty_action action;
+	const char *str;
+
+	alarm = value;
+
+	comp = E_CAL_COMPONENT (data);
+	priv = comp->priv;
+
+	prop = icalcomponent_get_first_property (alarm, ICAL_ACTION_PROPERTY);
+	if (!prop)
+		return;
+
+	action = icalproperty_get_action (prop);
+
+	switch (action) {
+	case ICAL_ACTION_DISPLAY:
+		/* Ensure we have a DESCRIPTION property */
+		prop = icalcomponent_get_first_property (alarm, ICAL_DESCRIPTION_PROPERTY);
+		if (prop) {
+			if (priv->summary.prop) {
+				icalproperty *xprop;
+
+				xprop = icalcomponent_get_first_property (alarm, ICAL_X_PROPERTY);
+				while (xprop) {
+					str = icalproperty_get_x_name (xprop);
+					if (!strcmp (str, "X-EVOLUTION-NEEDS-DESCRIPTION")) {
+						icalproperty_set_description (prop, icalproperty_get_summary(priv->summary.prop));
+
+						icalcomponent_remove_property (alarm, xprop);
+						icalproperty_free (xprop);
+						break;
+					}
+
+					xprop = icalcomponent_get_next_property (alarm, ICAL_X_PROPERTY);
+				}
+
+				break;
+			}
+		}
+
+		if (!priv->summary.prop) {
+			str = _("Untitled appointment");
+
+			/* add the X-EVOLUTION-NEEDS-DESCRIPTION property */
+			prop = icalproperty_new_x ("1");
+			icalproperty_set_x_name (prop, "X-EVOLUTION-NEEDS-DESCRIPTION");
+			icalcomponent_add_property (alarm, prop);
+		} else
+			str = icalproperty_get_summary (priv->summary.prop);
+
+		prop = icalproperty_new_description (str);
+		icalcomponent_add_property (alarm, prop);
+
+		break;
+
+	default:
+		break;
+		/* FIXME: add other action types here */
+	}
+}
+
+/* Ensures that alarm subcomponents have the mandatory properties they need,
+ * even when clients may not have set them properly.
+ */
+static void
+ensure_alarm_properties (ECalComponent *comp)
+{
+	ECalComponentPrivate *priv;
+
+	priv = comp->priv;
+
+	g_hash_table_foreach (priv->alarm_uid_hash, ensure_alarm_properties_cb, comp);
+}
+
+/**
+ * e_cal_component_commit_sequence:
+ * @comp: A calendar component object.
+ *
+ * Increments the sequence number property in a calendar component object if it
+ * needs it.  This needs to be done when any of a number of properties listed in
+ * RFC 2445 change values, such as the start and end dates of a component.
+ *
+ * This function must be called before calling e_cal_component_get_as_string() to
+ * ensure that the component is fully consistent.
+ **/
+void
+e_cal_component_commit_sequence (ECalComponent *comp)
+{
+	ECalComponentPrivate *priv;
+
+	g_return_if_fail (comp != NULL);
+	g_return_if_fail (E_IS_CAL_COMPONENT (comp));
+
+	priv = comp->priv;
+	g_return_if_fail (priv->icalcomp != NULL);
+
+	ensure_alarm_properties (comp);
+
+	if (!priv->need_sequence_inc)
+		return;
+
+	if (priv->sequence) {
+		int seq;
+
+		seq = icalproperty_get_sequence (priv->sequence);
+		icalproperty_set_sequence (priv->sequence, seq + 1);
+	} else {
+		/* The component had no SEQUENCE property, so assume that the
+		 * default would have been zero.  Since it needed incrementing
+		 * anyways, we use a value of 1 here.
+		 */
+		priv->sequence = icalproperty_new_sequence (1);
+		icalcomponent_add_property (priv->icalcomp, priv->sequence);
+	}
+
+	priv->need_sequence_inc = FALSE;
+}
+
+/**
+ * e_cal_component_abort_sequence:
+ * @comp: A calendar component object.
+ *
+ * Aborts the sequence change needed in the given calendar component, which
+ * means it will not require a sequence commit (via #e_cal_component_commit_sequence)
+ * even if the changes done require a sequence increment.
+ */
+void
+e_cal_component_abort_sequence (ECalComponent *comp)
+{
+	ECalComponentPrivate *priv;
+
+	g_return_if_fail (comp != NULL);
+	g_return_if_fail (E_IS_CAL_COMPONENT (comp));
+
+	priv = comp->priv;
+
+	priv->need_sequence_inc = FALSE;
+}
+
+/**
+ * e_cal_component_get_uid:
+ * @comp: A calendar component object.
+ * @uid: Return value for the UID string.
+ *
+ * Queries the unique identifier of a calendar component object.
+ **/
+void
+e_cal_component_get_uid (ECalComponent *comp, const char **uid)
+{
+	ECalComponentPrivate *priv;
+
+	g_return_if_fail (comp != NULL);
+	g_return_if_fail (E_IS_CAL_COMPONENT (comp));
+	g_return_if_fail (uid != NULL);
+
+	priv = comp->priv;
+	g_return_if_fail (priv->icalcomp != NULL);
+
+	/* This MUST exist, since we ensured that it did */
+	g_assert (priv->uid != NULL);
+
+	*uid = icalproperty_get_uid (priv->uid);
+}
+
+/**
+ * e_cal_component_set_uid:
+ * @comp: A calendar component object.
+ * @uid: Unique identifier.
+ *
+ * Sets the unique identifier string of a calendar component object.
+ **/
+void
+e_cal_component_set_uid (ECalComponent *comp, const char *uid)
+{
+	ECalComponentPrivate *priv;
+
+	g_return_if_fail (comp != NULL);
+	g_return_if_fail (E_IS_CAL_COMPONENT (comp));
+	g_return_if_fail (uid != NULL);
+
+	priv = comp->priv;
+	g_return_if_fail (priv->icalcomp != NULL);
+
+	/* This MUST exist, since we ensured that it did */
+	g_assert (priv->uid != NULL);
+
+	icalproperty_set_uid (priv->uid, (char *) uid);
+}
+
+/* Gets a text list value */
+static void
+get_attachment_list (GSList *attachment_list, GSList **al)
+{
+	GSList *l;
+
+	*al = NULL;
+
+	if (!attachment_list)
+		return;
+
+	for (l = attachment_list; l; l = l->next) {
+		struct attachment *attachment;
+		const char *data;
+
+		attachment = l->data;
+		g_assert (attachment->attach != NULL);
+
+		if (icalattach_get_is_url (attachment->attach)) {
+			/* FIXME : this ref count is screwed up
+			 * These structures are being leaked.
+			 */   
+			icalattach_ref (attachment->attach);
+			data = icalattach_get_url (attachment->attach);
+		}
+		else
+			data = NULL;
+		*al = g_slist_prepend (*al, (char *)data);
+	}
+
+	*al = g_slist_reverse (*al);
+}
+
+
+static void
+set_attachment_list (icalcomponent *icalcomp,
+		   GSList **attachment_list,
+		   GSList *al)
+{
+	GSList *l;
+
+	/* Remove old attachments */
+
+	if (*attachment_list) {
+		for (l = *attachment_list; l; l = l->next) {
+			struct attachment *attachment;
+
+			attachment = l->data;
+			g_assert (attachment->prop != NULL);
+			g_assert (attachment->attach != NULL);
+
+			icalcomponent_remove_property (icalcomp, attachment->prop);
+			icalproperty_free (attachment->prop);
+			g_free (attachment);
+		}
+
+		g_slist_free (*attachment_list);
+		*attachment_list = NULL;
+	}
+	/* Add in new attachments */
+
+	for (l = al; l; l = l->next) {
+		struct attachment *attachment;
+
+		attachment = g_new0 (struct attachment, 1);
+		attachment->attach = icalattach_new_from_url ((char *) l->data); 	
+		attachment->prop = icalproperty_new_attach (attachment->attach);
+		icalcomponent_add_property (icalcomp, attachment->prop);
+
+		*attachment_list = g_slist_prepend (*attachment_list, attachment);
+	}
+
+	*attachment_list = g_slist_reverse (*attachment_list);
+}
+
+
+/**
+ * e_cal_component_get_attachment_list: 
+ * @comp: A calendar component object. 
+ * @attachment_list: Return list of URLS to attachments.
+ * 
+ * Queries the attachment properties of the calendar component object. When done,
+ * the @attachment_list should be freed by calling #g_slist_free.
+ **/
+void
+e_cal_component_get_attachment_list (ECalComponent *comp, GSList **attachment_list)
+{
+	ECalComponentPrivate *priv;
+	
+	g_return_if_fail (comp != NULL);
+	g_return_if_fail (E_IS_CAL_COMPONENT (comp));
+	g_return_if_fail (attachment_list != NULL);
+
+	priv = comp->priv;
+	g_return_if_fail (priv->icalcomp != NULL);
+
+	get_attachment_list (priv->attachment_list, attachment_list);
+}
+
+/**
+ * e_cal_component_set_attachment_list:
+ * @comp: A calendar component object. 
+ * @attachment_list: list of urls to attachment pointers.
+ *
+ * This currently handles only attachments that are urls
+ * in the file system - not inline binaries.
+ *
+ * Sets the attachments of a calendar component object
+ **/
+void
+e_cal_component_set_attachment_list (ECalComponent *comp, GSList *attachment_list)
+{
+	ECalComponentPrivate *priv;
+
+	g_return_if_fail (comp != NULL);
+	g_return_if_fail (E_IS_CAL_COMPONENT (comp));
+
+	priv = comp->priv;
+	g_return_if_fail (priv->icalcomp != NULL);
+
+	set_attachment_list (priv->icalcomp, &priv->attachment_list, attachment_list);
+}
+
+/**
+ * e_cal_component_has_attachments:
+ * @comp: A calendar component object.
+ *
+ * Queries the component to see if it has attachments.
+ *
+ * Return value: TRUE if there are attachments, FALSE otherwise.
+ */
+gboolean
+e_cal_component_has_attachments (ECalComponent *comp)
+{
+	ECalComponentPrivate *priv;
+
+	g_return_val_if_fail (comp != NULL, FALSE);
+	g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), FALSE);
+
+	priv = comp->priv;
+
+	if (g_slist_length (priv->attachment_list) > 0)
+		return TRUE;
+	
+	return FALSE;
+}
+
+
+int 
+e_cal_component_get_num_attachments (ECalComponent *comp)
+{
+	ECalComponentPrivate *priv;
+
+	g_return_val_if_fail (comp != NULL, 0);
+	g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), 0);
+
+	priv = comp->priv;
+
+	return g_slist_length (priv->attachment_list) > 0;
+	
+}
+
+/**
+ * e_cal_component_get_categories:
+ * @comp: A calendar component object.
+ * @categories: Return holder for the categories.
+ *
+ * Queries the categories of the given calendar component. The categories
+ * are returned in the @categories argument, which, on success, will contain
+ * a comma-separated list of all categories set in the component.
+ **/
+void
+e_cal_component_get_categories (ECalComponent *comp, const char **categories)
+{
+	ECalComponentPrivate *priv;
+
+	g_return_if_fail (comp != NULL);
+	g_return_if_fail (E_IS_CAL_COMPONENT (comp));
+	g_return_if_fail (categories != NULL);
+
+	priv = comp->priv;
+	g_return_if_fail (priv->icalcomp != NULL);
+
+	if (priv->categories)
+		*categories = icalproperty_get_categories (priv->categories);
+	else
+		*categories = NULL;
+}
+
+/**
+ * e_cal_component_set_categories:
+ * @comp: A calendar component object.
+ * @categories: Comma-separated list of categories.
+ *
+ * Sets the list of categories for a calendar component.
+ **/
+void
+e_cal_component_set_categories (ECalComponent *comp, const char *categories)
+{
+	ECalComponentPrivate *priv;
+
+	g_return_if_fail (comp != NULL);
+	g_return_if_fail (E_IS_CAL_COMPONENT (comp));
+
+	priv = comp->priv;
+	g_return_if_fail (priv->icalcomp != NULL);
+
+	if (!categories || !(*categories)) {
+		if (priv->categories) {
+			icalcomponent_remove_property (priv->icalcomp, priv->categories);
+			icalproperty_free (priv->categories);
+			priv->url = NULL;
+		}
+
+		return;
+	}
+
+	if (priv->categories)
+		icalproperty_set_categories (priv->categories, (char *) categories);
+	else {
+		priv->categories = icalproperty_new_categories ((char *) categories);
+		icalcomponent_add_property (priv->icalcomp, priv->categories);
+	}
+}
+
+
+/**
+ * e_cal_component_get_categories_list:
+ * @comp: A calendar component object.
+ * @categ_list: Return value for the list of strings, where each string is a
+ * category. This should be freed using e_cal_component_free_categories_list().
+ *
+ * Queries the list of categories of a calendar component object.  Each element
+ * in the returned categ_list is a string with the corresponding category.
+ **/
+void
+e_cal_component_get_categories_list (ECalComponent *comp, GSList **categ_list)
+{
+	ECalComponentPrivate *priv;
+	const char *categories;
+	const char *p;
+	const char *cat_start;
+	char *str;
+
+	g_return_if_fail (comp != NULL);
+	g_return_if_fail (E_IS_CAL_COMPONENT (comp));
+	g_return_if_fail (categ_list != NULL);
+
+	priv = comp->priv;
+	g_return_if_fail (priv->icalcomp != NULL);
+
+	if (!priv->categories) {
+		*categ_list = NULL;
+		return;
+	}
+
+	categories = icalproperty_get_categories (priv->categories);
+	g_assert (categories != NULL);
+
+	cat_start = categories;
+	*categ_list = NULL;
+
+	for (p = categories; *p; p++)
+		if (*p == ',') {
+			str = g_strndup (cat_start, p - cat_start);
+			*categ_list = g_slist_prepend (*categ_list, str);
+
+			cat_start = p + 1;
+		}
+
+	str = g_strndup (cat_start, p - cat_start);
+	*categ_list = g_slist_prepend (*categ_list, str);
+
+	*categ_list = g_slist_reverse (*categ_list);
+}
+
+/* Creates a comma-delimited string of categories */
+static char *
+stringify_categories (GSList *categ_list)
+{
+	GString *s;
+	GSList *l;
+	char *str;
+
+	s = g_string_new (NULL);
+
+	for (l = categ_list; l; l = l->next) {
+		g_string_append (s, l->data);
+
+		if (l->next != NULL)
+			g_string_append (s, ",");
+	}
+
+	str = s->str;
+	g_string_free (s, FALSE);
+
+	return str;
+}
+
+/**
+ * e_cal_component_set_categories_list:
+ * @comp: A calendar component object.
+ * @categ_list: List of strings, one for each category.
+ *
+ * Sets the list of categories of a calendar component object.
+ **/
+void
+e_cal_component_set_categories_list (ECalComponent *comp, GSList *categ_list)
+{
+	ECalComponentPrivate *priv;
+	char *categories_str;
+
+	g_return_if_fail (comp != NULL);
+	g_return_if_fail (E_IS_CAL_COMPONENT (comp));
+
+	priv = comp->priv;
+	g_return_if_fail (priv->icalcomp != NULL);
+
+	if (!categ_list) {
+		if (priv->categories) {
+			icalcomponent_remove_property (priv->icalcomp, priv->categories);
+			icalproperty_free (priv->categories);
+		}
+
+		return;
+	}
+
+	/* Create a single string of categories */
+	categories_str = stringify_categories (categ_list);
+
+	/* Set the categories */
+	priv->categories = icalproperty_new_categories (categories_str);
+	g_free (categories_str);
+
+	icalcomponent_add_property (priv->icalcomp, priv->categories);
+}
+
+/**
+ * e_cal_component_get_classification:
+ * @comp: A calendar component object.
+ * @classif: Return value for the classification.
+ *
+ * Queries the classification of a calendar component object.  If the
+ * classification property is not set on this component, this function returns
+ * #E_CAL_COMPONENT_CLASS_NONE.
+ **/
+void
+e_cal_component_get_classification (ECalComponent *comp, ECalComponentClassification *classif)
+{
+	ECalComponentPrivate *priv;
+	icalproperty_class class;
+
+	g_return_if_fail (comp != NULL);
+	g_return_if_fail (E_IS_CAL_COMPONENT (comp));
+	g_return_if_fail (classif != NULL);
+
+	priv = comp->priv;
+	g_return_if_fail (priv->icalcomp != NULL);
+
+	if (!priv->classification) {
+		*classif = E_CAL_COMPONENT_CLASS_NONE;
+		return;
+	}
+
+	class = icalproperty_get_class (priv->classification);
+
+	switch (class)
+	{
+	case ICAL_CLASS_PUBLIC:
+	  *classif = E_CAL_COMPONENT_CLASS_PUBLIC;
+	  break;
+	case ICAL_CLASS_PRIVATE:
+	  *classif = E_CAL_COMPONENT_CLASS_PRIVATE;
+	  break;
+	case ICAL_CLASS_CONFIDENTIAL:
+	  *classif = E_CAL_COMPONENT_CLASS_CONFIDENTIAL;
+	  break;
+	default:
+	  *classif = E_CAL_COMPONENT_CLASS_UNKNOWN;
+	  break;
+	}
+}
+
+/**
+ * e_cal_component_set_classification:
+ * @comp: A calendar component object.
+ * @classif: Classification to use.
+ *
+ * Sets the classification property of a calendar component object.  To unset
+ * the property, specify E_CAL_COMPONENT_CLASS_NONE for @classif.
+ **/
+void
+e_cal_component_set_classification (ECalComponent *comp, ECalComponentClassification classif)
+{
+	ECalComponentPrivate *priv;
+	icalproperty_class class;
+
+	g_return_if_fail (comp != NULL);
+	g_return_if_fail (E_IS_CAL_COMPONENT (comp));
+	g_return_if_fail (classif != E_CAL_COMPONENT_CLASS_UNKNOWN);
+
+	priv = comp->priv;
+	g_return_if_fail (priv->icalcomp != NULL);
+
+	if (classif == E_CAL_COMPONENT_CLASS_NONE) {
+		if (priv->classification) {
+			icalcomponent_remove_property (priv->icalcomp, priv->classification);
+			icalproperty_free (priv->classification);
+			priv->classification = NULL;
+		}
+
+		return;
+	}
+
+	switch (classif) {
+	case E_CAL_COMPONENT_CLASS_PUBLIC:
+	  class = ICAL_CLASS_PUBLIC;
+		break;
+
+	case E_CAL_COMPONENT_CLASS_PRIVATE:
+	  class = ICAL_CLASS_PRIVATE;
+		break;
+
+	case E_CAL_COMPONENT_CLASS_CONFIDENTIAL:
+	  class = ICAL_CLASS_CONFIDENTIAL;
+		break;
+
+	default:
+		g_assert_not_reached ();
+		class = ICAL_CLASS_NONE;
+	}
+
+	if (priv->classification)
+		icalproperty_set_class (priv->classification, class);
+	else {
+		priv->classification = icalproperty_new_class (class);
+		icalcomponent_add_property (priv->icalcomp, priv->classification);
+	}
+}
+
+/* Gets a text list value */
+static void
+get_text_list (GSList *text_list,
+	       const char *(* get_prop_func) (const icalproperty *prop),
+	       GSList **tl)
+{
+	GSList *l;
+
+	*tl = NULL;
+
+	if (!text_list)
+		return;
+
+	for (l = text_list; l; l = l->next) {
+		struct text *text;
+		ECalComponentText *t;
+
+		text = l->data;
+		g_assert (text->prop != NULL);
+
+		t = g_new (ECalComponentText, 1);
+		t->value = (* get_prop_func) (text->prop);
+
+		if (text->altrep_param)
+			t->altrep = icalparameter_get_altrep (text->altrep_param);
+		else
+			t->altrep = NULL;
+
+		*tl = g_slist_prepend (*tl, t);
+	}
+
+	*tl = g_slist_reverse (*tl);
+}
+
+/* Sets a text list value */
+static void
+set_text_list (ECalComponent *comp,
+	       icalproperty *(* new_prop_func) (const char *value),
+	       GSList **text_list,
+	       GSList *tl)
+{
+	ECalComponentPrivate *priv;
+	GSList *l;
+
+	priv = comp->priv;
+
+	/* Remove old texts */
+
+	for (l = *text_list; l; l = l->next) {
+		struct text *text;
+
+		text = l->data;
+		g_assert (text->prop != NULL);
+
+		icalcomponent_remove_property (priv->icalcomp, text->prop);
+		icalproperty_free (text->prop);
+		g_free (text);
+	}
+
+	g_slist_free (*text_list);
+	*text_list = NULL;
+
+	/* Add in new texts */
+
+	for (l = tl; l; l = l->next) {
+		ECalComponentText *t;
+		struct text *text;
+
+		t = l->data;
+		g_return_if_fail (t->value != NULL);
+
+		text = g_new (struct text, 1);
+
+		text->prop = (* new_prop_func) ((char *) t->value);
+		icalcomponent_add_property (priv->icalcomp, text->prop);
+
+		if (t->altrep) {
+			text->altrep_param = icalparameter_new_altrep ((char *) t->altrep);
+			icalproperty_add_parameter (text->prop, text->altrep_param);
+		} else
+			text->altrep_param = NULL;
+
+		*text_list = g_slist_prepend (*text_list, text);
+	}
+
+	*text_list = g_slist_reverse (*text_list);
+}
+
+/**
+ * e_cal_component_get_comment_list:
+ * @comp: A calendar component object.
+ * @text_list: Return value for the comment properties and their parameters, as
+ * a list of #ECalComponentText structures.  This should be freed using the
+ * e_cal_component_free_text_list() function.
+ *
+ * Queries the comments of a calendar component object.  The comment property can
+ * appear several times inside a calendar component, and so a list of
+ * #ECalComponentText is returned.
+ **/
+void
+e_cal_component_get_comment_list (ECalComponent *comp, GSList **text_list)
+{
+	ECalComponentPrivate *priv;
+
+	g_return_if_fail (comp != NULL);
+	g_return_if_fail (E_IS_CAL_COMPONENT (comp));
+	g_return_if_fail (text_list != NULL);
+
+	priv = comp->priv;
+	g_return_if_fail (priv->icalcomp != NULL);
+
+	get_text_list (priv->comment_list, icalproperty_get_comment, text_list);
+}
+
+/**
+ * e_cal_component_set_comment_list:
+ * @comp: A calendar component object.
+ * @text_list: List of #ECalComponentText structures.
+ *
+ * Sets the comments of a calendar component object.  The comment property can
+ * appear several times inside a calendar component, and so a list of
+ * #ECalComponentText structures is used.
+ **/
+void
+e_cal_component_set_comment_list (ECalComponent *comp, GSList *text_list)
+{
+	ECalComponentPrivate *priv;
+
+	g_return_if_fail (comp != NULL);
+	g_return_if_fail (E_IS_CAL_COMPONENT (comp));
+
+	priv = comp->priv;
+	g_return_if_fail (priv->icalcomp != NULL);
+
+	set_text_list (comp, icalproperty_new_comment, &priv->comment_list, text_list);
+}
+
+/**
+ * e_cal_component_get_contact_list:
+ * @comp: A calendar component object.
+ * @text_list: Return value for the contact properties and their parameters, as
+ * a list of #ECalComponentText structures.  This should be freed using the
+ * e_cal_component_free_text_list() function.
+ *
+ * Queries the contact of a calendar component object.  The contact property can
+ * appear several times inside a calendar component, and so a list of
+ * #ECalComponentText is returned.
+ **/
+void
+e_cal_component_get_contact_list (ECalComponent *comp, GSList **text_list)
+{
+	ECalComponentPrivate *priv;
+
+	g_return_if_fail (comp != NULL);
+	g_return_if_fail (E_IS_CAL_COMPONENT (comp));
+	g_return_if_fail (text_list != NULL);
+
+	priv = comp->priv;
+	g_return_if_fail (priv->icalcomp != NULL);
+
+	get_text_list (priv->contact_list, icalproperty_get_contact, text_list);
+}
+
+/**
+ * e_cal_component_set_contact_list:
+ * @comp: A calendar component object.
+ * @text_list: List of #ECalComponentText structures.
+ *
+ * Sets the contact of a calendar component object.  The contact property can
+ * appear several times inside a calendar component, and so a list of
+ * #ECalComponentText structures is used.
+ **/
+void
+e_cal_component_set_contact_list (ECalComponent *comp, GSList *text_list)
+{
+	ECalComponentPrivate *priv;
+
+	g_return_if_fail (comp != NULL);
+	g_return_if_fail (E_IS_CAL_COMPONENT (comp));
+
+	priv = comp->priv;
+	g_return_if_fail (priv->icalcomp != NULL);
+
+	set_text_list (comp, icalproperty_new_contact, &priv->contact_list, text_list);
+}
+
+/* Gets a struct icaltimetype value */
+static void
+get_icaltimetype (icalproperty *prop,
+		  struct icaltimetype (* get_prop_func) (const icalproperty *prop),
+		  struct icaltimetype **t)
+{
+	if (!prop) {
+		*t = NULL;
+		return;
+	}
+
+	*t = g_new (struct icaltimetype, 1);
+	**t = (* get_prop_func) (prop);
+}
+
+/* Sets a struct icaltimetype value */
+static void
+set_icaltimetype (ECalComponent *comp, icalproperty **prop,
+		  icalproperty *(* prop_new_func) (struct icaltimetype v),
+		  void (* prop_set_func) (icalproperty *prop, struct icaltimetype v),
+		  struct icaltimetype *t)
+{
+	ECalComponentPrivate *priv;
+
+	priv = comp->priv;
+
+	if (!t) {
+		if (*prop) {
+			icalcomponent_remove_property (priv->icalcomp, *prop);
+			icalproperty_free (*prop);
+			*prop = NULL;
+		}
+
+		return;
+	}
+
+	if (*prop)
+		(* prop_set_func) (*prop, *t);
+	else {
+		*prop = (* prop_new_func) (*t);
+		icalcomponent_add_property (priv->icalcomp, *prop);
+	}
+}
+
+/**
+ * e_cal_component_get_completed:
+ * @comp: A calendar component object.
+ * @t: Return value for the completion date.  This should be freed using the
+ * e_cal_component_free_icaltimetype() function.
+ *
+ * Queries the date at which a calendar compoment object was completed.
+ **/
+void
+e_cal_component_get_completed (ECalComponent *comp, struct icaltimetype **t)
+{
+	ECalComponentPrivate *priv;
+
+	g_return_if_fail (comp != NULL);
+	g_return_if_fail (E_IS_CAL_COMPONENT (comp));
+	g_return_if_fail (t != NULL);
+
+	priv = comp->priv;
+	g_return_if_fail (priv->icalcomp != NULL);
+
+	get_icaltimetype (priv->completed, icalproperty_get_completed, t);
+}
+
+/**
+ * e_cal_component_set_completed:
+ * @comp: A calendar component object.
+ * @t: Value for the completion date.
+ *
+ * Sets the date at which a calendar component object was completed.
+ **/
+void
+e_cal_component_set_completed (ECalComponent *comp, struct icaltimetype *t)
+{
+	ECalComponentPrivate *priv;
+
+	g_return_if_fail (comp != NULL);
+	g_return_if_fail (E_IS_CAL_COMPONENT (comp));
+
+	priv = comp->priv;
+	g_return_if_fail (priv->icalcomp != NULL);
+
+	set_icaltimetype (comp, &priv->completed,
+			  icalproperty_new_completed,
+			  icalproperty_set_completed,
+			  t);
+}
+
+
+/**
+ * e_cal_component_get_created:
+ * @comp: A calendar component object.
+ * @t: Return value for the creation date.  This should be freed using the
+ * e_cal_component_free_icaltimetype() function.
+ *
+ * Queries the date in which a calendar component object was created in the
+ * calendar store.
+ **/
+void
+e_cal_component_get_created (ECalComponent *comp, struct icaltimetype **t)
+{
+	ECalComponentPrivate *priv;
+
+	g_return_if_fail (comp != NULL);
+	g_return_if_fail (E_IS_CAL_COMPONENT (comp));
+	g_return_if_fail (t != NULL);
+
+	priv = comp->priv;
+	g_return_if_fail (priv->icalcomp != NULL);
+
+	get_icaltimetype (priv->created, icalproperty_get_created, t);
+}
+
+/**
+ * e_cal_component_set_created:
+ * @comp: A calendar component object.
+ * @t: Value for the creation date.
+ *
+ * Sets the date in which a calendar component object is created in the calendar
+ * store.  This should only be used inside a calendar store application, i.e.
+ * not by calendar user agents.
+ **/
+void
+e_cal_component_set_created (ECalComponent *comp, struct icaltimetype *t)
+{
+	ECalComponentPrivate *priv;
+
+	g_return_if_fail (comp != NULL);
+	g_return_if_fail (E_IS_CAL_COMPONENT (comp));
+
+	priv = comp->priv;
+	g_return_if_fail (priv->icalcomp != NULL);
+
+	set_icaltimetype (comp, &priv->created,
+			  icalproperty_new_created,
+			  icalproperty_set_created,
+			  t);
+}
+
+/**
+ * e_cal_component_get_description_list:
+ * @comp: A calendar component object.
+ * @text_list: Return value for the description properties and their parameters,
+ * as a list of #ECalComponentText structures.  This should be freed using the
+ * e_cal_component_free_text_list() function.
+ *
+ * Queries the description of a calendar component object.  Journal components
+ * may have more than one description, and as such this function returns a list
+ * of #ECalComponentText structures.  All other types of components can have at
+ * most one description.
+ **/
+void
+e_cal_component_get_description_list (ECalComponent *comp, GSList **text_list)
+{
+	ECalComponentPrivate *priv;
+
+	g_return_if_fail (comp != NULL);
+	g_return_if_fail (E_IS_CAL_COMPONENT (comp));
+	g_return_if_fail (text_list != NULL);
+
+	priv = comp->priv;
+	g_return_if_fail (priv->icalcomp != NULL);
+
+	get_text_list (priv->description_list, icalproperty_get_description, text_list);
+}
+
+/**
+ * e_cal_component_set_description_list:
+ * @comp: A calendar component object.
+ * @text_list: List of #ECalComponentSummary structures.
+ *
+ * Sets the description of a calendar component object.  Journal components may
+ * have more than one description, and as such this function takes in a list of
+ * #ECalComponentDescription structures.  All other types of components can have
+ * at most one description.
+ **/
+void
+e_cal_component_set_description_list (ECalComponent *comp, GSList *text_list)
+{
+	ECalComponentPrivate *priv;
+
+	g_return_if_fail (comp != NULL);
+	g_return_if_fail (E_IS_CAL_COMPONENT (comp));
+
+	priv = comp->priv;
+	g_return_if_fail (priv->icalcomp != NULL);
+
+	set_text_list (comp, icalproperty_new_description, &priv->description_list, text_list);
+}
+
+/* Gets a date/time and timezone pair */
+static void
+get_datetime (struct datetime *datetime,
+	      struct icaltimetype (* get_prop_func) (const icalproperty *prop),
+	      ECalComponentDateTime *dt)
+{
+	if (datetime->prop) {
+		dt->value = g_new (struct icaltimetype, 1);
+		*dt->value = (* get_prop_func) (datetime->prop);
+	} else
+		dt->value = NULL;
+
+	/* If the icaltimetype has is_utc set, we set "UTC" as the TZID.
+	   This makes the timezone code simpler. */
+	if (datetime->tzid_param)
+		dt->tzid = g_strdup (icalparameter_get_tzid (datetime->tzid_param));
+	else if (dt->value && dt->value->is_utc)
+		dt->tzid = g_strdup ("UTC");
+	else
+		dt->tzid = NULL;
+}
+
+/* Sets a date/time and timezone pair */
+static void
+set_datetime (ECalComponent *comp, struct datetime *datetime,
+	      icalproperty *(* prop_new_func) (struct icaltimetype v),
+	      void (* prop_set_func) (icalproperty * prop, struct icaltimetype v),
+	      ECalComponentDateTime *dt)
+{
+	ECalComponentPrivate *priv;
+
+	priv = comp->priv;
+
+	/* If we are setting the property to NULL (i.e. removing it), then
+	   we remove it if it exists. */
+	if (!dt) {
+		if (datetime->prop) {
+			icalcomponent_remove_property (priv->icalcomp, datetime->prop);
+			icalproperty_free (datetime->prop);
+
+			datetime->prop = NULL;
+			datetime->tzid_param = NULL;
+		}
+
+		return;
+	}
+
+	g_return_if_fail (dt->value != NULL);
+
+	/* If the TZID is set to "UTC", we set the is_utc flag. */
+	if (dt->tzid && !strcmp (dt->tzid, "UTC"))
+		dt->value->is_utc = 1;
+	else
+		dt->value->is_utc = 0;
+
+	if (datetime->prop) {
+		(* prop_set_func) (datetime->prop, *dt->value);
+	} else {
+		datetime->prop = (* prop_new_func) (*dt->value);
+		icalcomponent_add_property (priv->icalcomp, datetime->prop);
+	}
+
+	/* If the TZID is set to "UTC", we don't want to save the TZID. */
+	if (dt->tzid && strcmp (dt->tzid, "UTC")) {
+		g_assert (datetime->prop != NULL);
+
+		if (datetime->tzid_param) {
+			ica