[evolution-data-server] Bug #543069 - Support detached instances in CalDAV



commit db2483e41abc316558eeb73ead339d853b988b0a
Author: Milan Crha <mcrha redhat com>
Date:   Mon May 25 16:49:01 2009 +0200

    Bug #543069 - Support detached instances in CalDAV
    
    Improved support for recurring events in CalDAV, together with some
    other related improvements and a leak fix
    in e-cal-backend-util.c:is_attendee_declined
---
 calendar/backends/caldav/e-cal-backend-caldav.c | 1375 ++++++++++++++++-------
 calendar/libedata-cal/e-cal-backend-util.c      |    4 +-
 2 files changed, 976 insertions(+), 403 deletions(-)

diff --git a/calendar/backends/caldav/e-cal-backend-caldav.c b/calendar/backends/caldav/e-cal-backend-caldav.c
index 107c258..78002ca 100644
--- a/calendar/backends/caldav/e-cal-backend-caldav.c
+++ b/calendar/backends/caldav/e-cal-backend-caldav.c
@@ -97,11 +97,6 @@ struct _ECalBackendCalDAVPrivate {
 	/* well, guess what */
 	gboolean read_only;
 
-	/* whehter the synch function
-	 * should report changes to the
-	 * backend */
-	gboolean report_changes;
-
 	/* clandar uri */
 	char *uri;
 
@@ -250,6 +245,9 @@ static ECalBackendSyncClass *parent_class = NULL;
 static icaltimezone *caldav_internal_get_default_timezone (ECalBackend *backend);
 static icaltimezone *caldav_internal_get_timezone (ECalBackend *backend, const char *tzid);
 
+static gboolean remove_comp_from_cache (ECalBackendCalDAV *cbdav, const char *uid, const char *rid);
+static gboolean put_comp_to_cache (ECalBackendCalDAV *cbdav, icalcomponent *icalcomp, const char *href, const char *etag);
+
 /* ************************************************************************* */
 /* Misc. utility functions */
 #define X_E_CALDAV "X-EVOLUTION-CALDAV-"
@@ -266,19 +264,23 @@ icomp_x_prop_set (icalcomponent *comp, const char *key, const char *value)
 		const char *str = icalproperty_get_x_name (xprop);
 
 		if (!strcmp (str, key)) {
-			icalcomponent_remove_property (comp, xprop);
-			icalproperty_free (xprop);
+			if (value) {
+				icalproperty_set_value_from_string (xprop, value, "NO");
+			} else {
+				icalcomponent_remove_property (comp, xprop);
+				icalproperty_free (xprop);
+			}
 			break;
 		}
 
 		xprop = icalcomponent_get_next_property (comp, ICAL_X_PROPERTY);
 	}
 
-	/* couldnt we be a bit smarter here and reuse the property? */
-
-	xprop = icalproperty_new_x (value);
-	icalproperty_set_x_name (xprop, key);
-	icalcomponent_add_property (comp, xprop);
+	if (!xprop && value) {
+		xprop = icalproperty_new_x (value);
+		icalproperty_set_x_name (xprop, key);
+		icalcomponent_add_property (comp, xprop);
+	}
 }
 
 
@@ -307,14 +309,12 @@ icomp_x_prop_get (icalcomponent *comp, const char *key)
 	return NULL;
 }
 
-
+/* passing NULL as 'href' removes the property */
 static void
-e_cal_component_set_href (ECalComponent *comp, const char *href)
+ecalcomp_set_href (ECalComponent *comp, const char *href)
 {
 	icalcomponent *icomp;
 
-	g_return_if_fail (href != NULL);
-
 	icomp = e_cal_component_get_icalcomponent (comp);
 	g_return_if_fail (icomp != NULL);
 
@@ -322,7 +322,7 @@ e_cal_component_set_href (ECalComponent *comp, const char *href)
 }
 
 static char *
-e_cal_component_get_href (ECalComponent *comp)
+ecalcomp_get_href (ECalComponent *comp)
 {
 	icalcomponent *icomp;
 	char          *str;
@@ -336,14 +336,12 @@ e_cal_component_get_href (ECalComponent *comp)
 	return str;
 }
 
-
+/* passing NULL as 'etag' removes the property */
 static void
-e_cal_component_set_etag (ECalComponent *comp, const char *etag)
+ecalcomp_set_etag (ECalComponent *comp, const char *etag)
 {
 	icalcomponent *icomp;
 
-	g_return_if_fail (etag != NULL);
-
 	icomp = e_cal_component_get_icalcomponent (comp);
 	g_return_if_fail (icomp != NULL);
 
@@ -351,7 +349,7 @@ e_cal_component_set_etag (ECalComponent *comp, const char *etag)
 }
 
 static char *
-e_cal_component_get_etag (ECalComponent *comp)
+ecalcomp_get_etag (ECalComponent *comp)
 {
 	icalcomponent *icomp;
 	char          *str;
@@ -365,23 +363,22 @@ e_cal_component_get_etag (ECalComponent *comp)
 	return str;
 }
 
-typedef enum {
+/*typedef enum {
 
-	/* object is in synch,
-	 * now isnt that ironic? :) */
-	E_CAL_COMPONENT_IN_SYNCH = 0,
+	/ * object is in synch,
+	 * now isnt that ironic? :) * /
+	ECALCOMP_IN_SYNCH = 0,
 
-	/* local changes */
-	E_CAL_COMPONENT_LOCALLY_CREATED,
-	E_CAL_COMPONENT_LOCALLY_DELETED,
-	E_CAL_COMPONENT_LOCALLY_MODIFIED
+	/ * local changes * /
+	ECALCOMP_LOCALLY_CREATED,
+	ECALCOMP_LOCALLY_DELETED,
+	ECALCOMP_LOCALLY_MODIFIED
 
-} ECalComponentSyncState;
+} ECalCompSyncState;
 
-/* oos = out of synch */
+/ * oos = out of synch * /
 static void
-e_cal_component_set_synch_state (ECalComponent          *comp,
-				 ECalComponentSyncState  state)
+ecalcomp_set_synch_state (ECalComponent *comp, ECalCompSyncState state)
 {
 	icalcomponent *icomp;
 	char          *state_string;
@@ -393,14 +390,14 @@ e_cal_component_set_synch_state (ECalComponent          *comp,
 	icomp_x_prop_set (icomp, X_E_CALDAV "ETAG", state_string);
 
 	g_free (state_string);
-}
+}*/
 
 
 /* gen uid, set it internally and report it back so we can instantly
  * use it
  * and btw FIXME!!! */
 static char *
-e_cal_component_gen_href (ECalComponent *comp)
+ecalcomp_gen_href (ECalComponent *comp)
 {
 	char *href, *iso;
 
@@ -500,7 +497,7 @@ check_state (ECalBackendCalDAV *cbdav, gboolean *online)
 /* XML Parsing code */
 
 static xmlXPathObjectPtr
-xpath_eval (xmlXPathContextPtr ctx, char *format, ...)
+xpath_eval (xmlXPathContextPtr ctx, const char *format, ...)
 {
 	xmlXPathObjectPtr  result;
 	va_list            args;
@@ -1207,13 +1204,14 @@ caldav_server_get_object (ECalBackendCalDAV *cbdav, CalDAVObject *object)
 
 	g_free (object->cdata);
 	object->cdata = g_strdup (message->response_body->data);
+
 	g_object_unref (message);
 
 	return result;
 }
 
 static ECalBackendSyncStatus
-caldav_server_put_object (ECalBackendCalDAV *cbdav, CalDAVObject *object)
+caldav_server_put_object (ECalBackendCalDAV *cbdav, CalDAVObject *object, icalcomponent *icalcomp)
 {
 	ECalBackendCalDAVPrivate *priv;
 	ECalBackendSyncStatus     result;
@@ -1261,17 +1259,24 @@ caldav_server_put_object (ECalBackendCalDAV *cbdav, CalDAVObject *object)
 
 		/* there was a redirect, update href properly */
 		if (file) {
+			char *decoded;
+
 			g_free (object->href);
-			object->href = soup_uri_encode (file + 1, NULL);
+
+			decoded = soup_uri_decode (file + 1);
+			object->href = soup_uri_encode (decoded ? decoded : (file + 1), NULL);
+
+			g_free (decoded);
 		}
 
 		g_free (uri);
 	}
 
-	/* FIXME: sepcial case precondition errors ?*/
 	result = status_code_to_result (message->status_code, priv);
 
 	if (result == GNOME_Evolution_Calendar_Success) {
+		gboolean was_get = FALSE;
+
 		hdr = soup_message_headers_get (message->response_headers, "ETag");
 		if (hdr != NULL) {
 			g_free (object->etag);
@@ -1284,12 +1289,36 @@ caldav_server_put_object (ECalBackendCalDAV *cbdav, CalDAVObject *object)
 				char *file = strrchr (hdr, '/');
 
 				if (file) {
+					char *decoded;
+
 					g_free (object->href);
-					object->href = soup_uri_encode (file + 1, NULL);
+
+					decoded = soup_uri_decode (file + 1);
+					object->href = soup_uri_encode (decoded ? decoded : (file + 1), NULL);
+
+					g_free (decoded);
 				}
 			}
 
 			result = caldav_server_get_object (cbdav, object);
+			was_get = TRUE;
+		}
+
+		if (result == GNOME_Evolution_Calendar_Success) {
+			icalcomponent *use_comp = NULL;
+
+			if (object->cdata && was_get) {
+				/* maybe server also modified component, thus rather store the server's */
+				use_comp = icalparser_parse_string (object->cdata);
+			}
+
+			if (!use_comp)
+				use_comp = icalcomp;
+
+			put_comp_to_cache (cbdav, use_comp, object->href, object->etag);
+
+			if (use_comp != icalcomp)
+				icalcomponent_free (use_comp);
 		}
 	}
 
@@ -1336,22 +1365,47 @@ caldav_server_delete_object (ECalBackendCalDAV *cbdav, CalDAVObject *object)
 /* ************************************************************************* */
 /* Synchronization foo */
 
+struct put_to_cache_data
+{
+	icalcomponent *icomp;
+	char *href;
+	char *etag;
+};
+
+static void
+put_to_cache_data_and_free_cb (gpointer pdata, gpointer user_data)
+{
+	struct put_to_cache_data *data = pdata;
+	ECalBackendCalDAV *cbdav = user_data;
+
+	g_return_if_fail (data != NULL);
+	g_return_if_fail (data->icomp != NULL);
+
+	put_comp_to_cache (cbdav, data->icomp, data->href, data->etag);
+
+	icalcomponent_free (data->icomp);
+	g_free (data->href);
+	g_free (data->etag);
+	g_free (data);
+}
+
+static gboolean extract_timezones (ECalBackendCalDAV *cbdav, icalcomponent *icomp);
+
 static gboolean
 synchronize_object (ECalBackendCalDAV *cbdav,
 		    CalDAVObject      *object,
 		    ECalComponent     *old_comp,
 		    GList            **created,
-		    GList            **modified)
+		    GList            **modified,
+		    GList            **put_to_cache)
 {
 	ECalBackendCalDAVPrivate *priv;
 	ECalBackendSyncStatus     result;
 	ECalBackend              *bkend;
-	ECalComponent            *comp;
 	icalcomponent 		 *icomp, *subcomp;
 	icalcomponent_kind        kind;
 	gboolean                  res;
 
-	comp = NULL;
 	res  = TRUE;
 	result  = caldav_server_get_object (cbdav, object);
 
@@ -1364,58 +1418,48 @@ synchronize_object (ECalBackendCalDAV *cbdav,
 	kind  = icalcomponent_isa (icomp);
 	bkend = E_CAL_BACKEND (cbdav);
 
+	extract_timezones (cbdav, icomp);
+
 	if (kind == ICAL_VCALENDAR_COMPONENT) {
+		ECalComponent *comp;
+		struct put_to_cache_data *data;
+
 		kind = e_cal_backend_get_kind (bkend);
-		subcomp = icalcomponent_get_first_component (icomp, kind);
+		res = FALSE;
+
+		for (subcomp = icalcomponent_get_first_component (icomp, kind);
+		     subcomp;
+		     subcomp = icalcomponent_get_next_component (icomp, kind)) {
+			res = TRUE;
 
-		if (!subcomp) {
-			res = FALSE;
-		} else {
 			comp = e_cal_component_new ();
-			res = e_cal_component_set_icalcomponent (comp,
-						   icalcomponent_new_clone (subcomp));
-			if (res) {
-				icaltimezone *zone = icaltimezone_new ();
-
-				e_cal_component_set_href (comp, object->href);
-				e_cal_component_set_etag (comp, object->etag);
-
-				for (subcomp = icalcomponent_get_first_component (icomp, ICAL_VTIMEZONE_COMPONENT);
-				    subcomp;
-				    subcomp = icalcomponent_get_next_component (icomp, ICAL_VTIMEZONE_COMPONENT)) {
-					/* copy timezones of the component to our cache to have it available later */
-					if (icaltimezone_set_component (zone, subcomp))
-						e_cal_backend_cache_put_timezone (priv->cache, zone);
-				}
+			if (e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (subcomp))) {
+				ecalcomp_set_href (comp, object->href);
+				ecalcomp_set_etag (comp, object->etag);
 
-				icaltimezone_free (zone, TRUE);
-			} else {
-				g_object_unref (comp);
-				comp = NULL;
+				if (old_comp == NULL) {
+					*created = g_list_prepend (*created, g_object_ref (comp));
+				} else {
+					/* they will be in the opposite order in the list */
+					*modified = g_list_prepend (*modified, g_object_ref (old_comp));
+					*modified = g_list_prepend (*modified, g_object_ref (comp));
+				}
 			}
-		}
-	} else {
-		res = FALSE;
-	}
 
-	icalcomponent_free (icomp);
+			g_object_unref (comp);
+		}
 
-	if (!res) {
-		return res;
-	}
+		data = g_new0 (struct put_to_cache_data, 1);
+		data->icomp = icomp;
+		data->href = g_strdup (object->href);
+		data->etag = g_strdup (object->etag);
 
-	if (priv->report_changes) {
-		if (old_comp == NULL) {
-			*created = g_list_prepend (*created, g_object_ref (comp));
-		} else {
-			/* they will be in the opposite order in the list */
-			*modified = g_list_prepend (*modified, g_object_ref (old_comp));
-			*modified = g_list_prepend (*modified, g_object_ref (comp));
-		}
+		*put_to_cache = g_list_prepend (*put_to_cache, data);
+	} else {
+		res = FALSE;
+		icalcomponent_free (icomp);
 	}
 
-	g_object_unref (comp);
-
 	return res;
 }
 
@@ -1432,7 +1476,7 @@ synchronize_cache (ECalBackendCalDAV *cbdav)
 	CalDAVObject             *sobjs;
 	CalDAVObject             *object;
 	GHashTable               *hindex;
-	GList                    *cobjs, *created = NULL, *modified = NULL;
+	GList                    *cobjs, *created = NULL, *modified = NULL, *put_to_cache = NULL;
 	GList                    *citer;
 	gboolean                  res;
 	int			  len;
@@ -1462,17 +1506,23 @@ synchronize_cache (ECalBackendCalDAV *cbdav)
 		ECalComponent *ccomp = E_CAL_COMPONENT (citer->data);
 		char *href;
 
-		href = e_cal_component_get_href (ccomp);
+		href = ecalcomp_get_href (ccomp);
 
 		if (href == NULL) {
 			g_warning ("href of object NULL :(");
 			continue;
 		}
 
+		/* prefer master object from a detached instance */
+		if (g_hash_table_lookup (hindex, href) && e_cal_component_is_instance (ccomp)) {
+			g_free (href);
+			continue;
+		}
+		
 		g_hash_table_insert (hindex, (gpointer) href, ccomp);
 	}
 
-	/* see if we have to upate or add some objects */
+	/* see if we have to update or add some objects */
 	for (i = 0, object = sobjs; i < len; i++, object++) {
 		ECalComponent *ccomp;
 		char *etag = NULL;
@@ -1488,11 +1538,11 @@ synchronize_cache (ECalBackendCalDAV *cbdav)
 		ccomp = g_hash_table_lookup (hindex, object->href);
 
 		if (ccomp != NULL) {
-			etag = e_cal_component_get_etag (ccomp);
+			etag = ecalcomp_get_etag (ccomp);
 		}
 
 		if (!etag || !etags_match (etag, object->etag)) {
-			res = synchronize_object (cbdav, object, ccomp, &created, &modified);
+			res = synchronize_object (cbdav, object, ccomp, &created, &modified, &put_to_cache);
 		}
 
 		if (res) {
@@ -1507,38 +1557,42 @@ synchronize_cache (ECalBackendCalDAV *cbdav)
 
 	/* remove old (not on server anymore) items from cache... */
 	for (citer = cobjs; citer; citer = g_list_next (citer)) {
-		ECalComponent *comp;
-		const char *uid = NULL;
+		ECalComponent *comp = E_CAL_COMPONENT (citer->data);
 
-		comp = E_CAL_COMPONENT (citer->data);
-		e_cal_component_get_uid (comp, &uid);
+		/* keep detached instances in a cache, they will be removed with the master object */
+		if (!e_cal_component_is_instance (comp)) {
+			const char *uid = NULL;
 
-		if (e_cal_backend_cache_remove_component (bcache, uid, NULL) &&
-		    priv->report_changes) {
-			char *str = e_cal_component_get_as_string (comp);
-			ECalComponentId *id = e_cal_component_get_id (comp);
+			e_cal_component_get_uid (comp, &uid);
 
-			e_cal_backend_notify_object_removed (E_CAL_BACKEND (cbdav),
-							     id, str, NULL);
-			e_cal_component_free_id (id);
-			g_free (str);
+			if (remove_comp_from_cache (cbdav, uid, NULL)) {
+				char *str = e_cal_component_get_as_string (comp);
+				ECalComponentId *id = e_cal_component_get_id (comp);
+
+				e_cal_backend_notify_object_removed (E_CAL_BACKEND (cbdav), id, str, NULL);
+				e_cal_component_free_id (id);
+				g_free (str);
+			}
 		}
 
 		g_object_unref (comp);
 	}
 
-	/* ...then notify created and modified components */
+	/* ... then update cache with new objects... */
+	g_list_foreach (put_to_cache, put_to_cache_data_and_free_cb, cbdav);
+
+	/* ... then notify created ... */
 	for (citer = created; citer; citer = citer->next) {
 		ECalComponent *comp = citer->data;
 		char *comp_str = e_cal_component_get_as_string (comp);
 
-		if (e_cal_backend_cache_put_component (priv->cache, comp))
-			e_cal_backend_notify_object_created (bkend, comp_str);
+		e_cal_backend_notify_object_created (bkend, comp_str);
 
 		g_free (comp_str);
 		g_object_unref (comp);
 	}
 
+	/* ... and modified components */
 	for (citer = modified; citer; citer = citer->next) {
 		ECalComponent *comp, *old_comp;
 		char *new_str, *old_str;
@@ -1551,8 +1605,7 @@ synchronize_cache (ECalBackendCalDAV *cbdav)
 		new_str = e_cal_component_get_as_string (comp);
 		old_str = e_cal_component_get_as_string (old_comp);
 
-		if (e_cal_backend_cache_put_component (priv->cache, comp))
-			e_cal_backend_notify_object_modified (bkend, old_str, new_str);
+		e_cal_backend_notify_object_modified (bkend, old_str, new_str);
 
 		g_free (new_str);
 		g_free (old_str);
@@ -1564,6 +1617,7 @@ synchronize_cache (ECalBackendCalDAV *cbdav)
 	g_list_free (cobjs);
 	g_list_free (created);
 	g_list_free (modified);
+	g_list_free (put_to_cache);
 }
 
 /* ************************************************************************* */
@@ -1800,7 +1854,6 @@ initialize_backend (ECalBackendCalDAV *cbdav)
 			result = GNOME_Evolution_Calendar_OtherError;
 		}
 
-		priv->report_changes = TRUE;
 		priv->synch_slave = slave;
 	}
 out:
@@ -1936,29 +1989,319 @@ caldav_remove (ECalBackendSync *backend,
 	return GNOME_Evolution_Calendar_Success;
 }
 
+static void
+remove_comp_from_cache_cb (gpointer value, gpointer user_data)
+{
+	ECalComponent *comp = value;
+	ECalBackendCache *cache = user_data;
+	ECalComponentId *id;
+
+	g_return_if_fail (comp != NULL);
+	g_return_if_fail (cache != NULL);
+
+	id = e_cal_component_get_id (comp);
+	g_return_if_fail (id != NULL);
+
+	e_cal_backend_cache_remove_component (cache, id->uid, id->rid);
+	e_cal_component_free_id (id);
+}
+
+static gboolean
+remove_comp_from_cache (ECalBackendCalDAV *cbdav, const char *uid, const char *rid)
+{
+	ECalBackendCalDAVPrivate *priv;
+	gboolean res = FALSE;
+
+	priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
+
+	if (!rid || !*rid) {
+		/* get with detached instances */
+		GSList *objects = e_cal_backend_cache_get_components_by_uid (priv->cache, uid);
+
+		if (objects) {
+			g_slist_foreach (objects, (GFunc)remove_comp_from_cache_cb, priv->cache);
+			g_slist_foreach (objects, (GFunc)g_object_unref, NULL);
+			g_slist_free (objects);
+
+			res = TRUE;
+		}
+	} else {
+		res = e_cal_backend_cache_remove_component (priv->cache, uid, rid);
+	}
+
+	return res;
+}
+
+static void
+add_detached_recur_to_vcalendar_cb (gpointer value, gpointer user_data)
+{
+	icalcomponent *recurrence = e_cal_component_get_icalcomponent (value);
+	icalcomponent *vcalendar = user_data;
+
+	icalcomponent_add_component (
+		vcalendar,
+		icalcomponent_new_clone (recurrence));
+}
+
+static gint
+sort_master_first (gconstpointer a, gconstpointer b)
+{
+	icalcomponent *ca, *cb;
+
+	ca = e_cal_component_get_icalcomponent ((ECalComponent *)a);
+	cb = e_cal_component_get_icalcomponent ((ECalComponent *)b);
+
+	if (!ca) {
+		if (!cb)
+			return 0;
+		else
+			return -1;
+	} else if (!cb) {
+		return 1;
+	}
+
+	return icaltime_compare (icalcomponent_get_recurrenceid (ca), icalcomponent_get_recurrenceid (cb));
+}
+
+/* Returns new icalcomponent, with all detached instances stored in a cache.
+   The cache lock should be locked when called this function.
+*/
+static icalcomponent *
+get_comp_from_cache (ECalBackendCalDAV *cbdav, const char *uid, const char *rid, char **href, char **etag)
+{
+	ECalBackendCalDAVPrivate *priv;
+	icalcomponent *icalcomp = NULL;
+
+	priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
+
+	if (rid == NULL || !*rid) {
+		/* get with detached instances */
+		GSList *objects = e_cal_backend_cache_get_components_by_uid (priv->cache, uid);
+
+		if (!objects) {
+			return NULL;
+		}
+
+		if (g_slist_length (objects) == 1) {
+			ECalComponent *comp = objects->data;
+
+			/* will be unreffed a bit later */
+			if (comp)
+				icalcomp = icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp));
+		} else {
+			/* if we have detached recurrences, return a VCALENDAR */
+			icalcomp = e_cal_util_new_top_level ();
+
+			objects = g_slist_sort (objects, sort_master_first);
+
+			/* add all detached recurrences and the master object */
+			g_slist_foreach (objects, add_detached_recur_to_vcalendar_cb, icalcomp);
+		}
+
+		/* every component has set same href and etag, thus it doesn't matter where it will be read */
+		if (href)
+			*href = ecalcomp_get_href (objects->data);
+		if (etag)
+			*etag = ecalcomp_get_etag (objects->data);
+
+		g_slist_foreach (objects, (GFunc)g_object_unref, NULL);
+		g_slist_free (objects);
+	} else {
+		/* get the exact object */
+		ECalComponent *comp = e_cal_backend_cache_get_component (priv->cache, uid, rid);
+
+		if (comp) {
+			icalcomp = icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp));
+			if (href)
+				*href = ecalcomp_get_href (comp);
+			if (etag)
+				*etag = ecalcomp_get_etag (comp);
+			g_object_unref (comp);
+		}
+	}
+
+	return icalcomp;
+}
+
+static gboolean
+put_comp_to_cache (ECalBackendCalDAV *cbdav, icalcomponent *icalcomp, const char *href, const char *etag)
+{
+	ECalBackendCalDAVPrivate *priv;
+	icalcomponent_kind my_kind;
+	ECalComponent *comp;
+	gboolean res = FALSE;
+
+	g_return_val_if_fail (cbdav != NULL, FALSE);
+	g_return_val_if_fail (icalcomp != NULL, FALSE);
+
+	priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
+
+	my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
+	comp = e_cal_component_new ();
+
+	if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
+		icalcomponent *subcomp;
+
+		/* remove all old components from the cache first */
+		for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
+		     subcomp;
+		     subcomp = icalcomponent_get_next_component (icalcomp, my_kind)) {
+			remove_comp_from_cache (cbdav, icalcomponent_get_uid (subcomp), NULL);
+		}
+
+		/* then put new. It's because some detached instances could be removed on the server. */
+		for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
+		     subcomp;
+		     subcomp = icalcomponent_get_next_component (icalcomp, my_kind)) {
+			/* because reusing the same comp doesn't clear recur_id member properly */
+			g_object_unref (comp);
+			comp = e_cal_component_new ();
+
+			if (e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (subcomp))) {
+				if (href)
+					ecalcomp_set_href (comp, href);
+				if (etag)
+					ecalcomp_set_etag (comp, etag);
+
+				if (e_cal_backend_cache_put_component (priv->cache, comp))
+					res = TRUE;
+			}
+		}
+	} else if (icalcomponent_isa (icalcomp) == my_kind) {
+		remove_comp_from_cache (cbdav, icalcomponent_get_uid (icalcomp), NULL);
+
+		if (e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (icalcomp))) {
+			if (href)
+				ecalcomp_set_href (comp, href);
+			if (etag)
+				ecalcomp_set_etag (comp, etag);
+
+			res = e_cal_backend_cache_put_component (priv->cache, comp);
+		}
+	}
+
+	g_object_unref (comp);
+
+	return res;
+}
+
+static void
+remove_property (gpointer prop, gpointer icomp)
+{
+	icalcomponent_remove_property (icomp, prop);
+	icalproperty_free (prop);
+}
+
+static void
+strip_x_evolution_caldav (icalcomponent *icomp)
+{
+	icalproperty *prop;
+	GSList *to_remove = NULL;
+
+	g_return_if_fail (icomp != NULL);
+	g_return_if_fail (icalcomponent_isa (icomp) != ICAL_VCALENDAR_COMPONENT);
+
+	for (prop = icalcomponent_get_first_property (icomp, ICAL_X_PROPERTY);
+	     prop;
+	     prop = icalcomponent_get_next_property (icomp, ICAL_X_PROPERTY)) {
+		if (g_str_has_prefix (icalproperty_get_x_name (prop), X_E_CALDAV)) {
+			to_remove = g_slist_prepend (to_remove, prop);
+		}
+	}
+
+	g_slist_foreach (to_remove, remove_property, icomp);
+	g_slist_free (to_remove);
+}
+
+/* callback for icalcomponent_foreach_tzid */
+typedef struct {
+	ECalBackendCache *cache;
+	icalcomponent *vcal_comp;
+	icalcomponent *icalcomp;
+} ForeachTzidData;
+
+static void
+add_timezone_cb (icalparameter *param, void *data)
+{
+	icaltimezone *tz;
+	const char *tzid;
+	icalcomponent *vtz_comp;
+	ForeachTzidData *f_data = (ForeachTzidData *) data;
+
+	tzid = icalparameter_get_tzid (param);
+	if (!tzid)
+		return;
+
+	tz = icalcomponent_get_timezone (f_data->vcal_comp, tzid);
+	if (tz)
+		return;
+
+	tz = icalcomponent_get_timezone (f_data->icalcomp, tzid);
+	if (!tz) {
+		tz = icaltimezone_get_builtin_timezone_from_tzid (tzid);
+		if (!tz)
+			tz = (icaltimezone *) e_cal_backend_cache_get_timezone (f_data->cache, tzid);
+		if (!tz)
+			return;
+	}
+
+	vtz_comp = icaltimezone_get_component (tz);
+	if (!vtz_comp)
+		return;
+
+	icalcomponent_add_component (f_data->vcal_comp,
+				     icalcomponent_new_clone (vtz_comp));
+}
+
+static void
+add_timezones_from_component (ECalBackendCalDAV *cbdav, icalcomponent *vcal_comp, icalcomponent *icalcomp)
+{
+	ForeachTzidData f_data;
+	ECalBackendCalDAVPrivate *priv;
+
+	g_return_if_fail (cbdav != NULL);
+	g_return_if_fail (vcal_comp != NULL);
+	g_return_if_fail (icalcomp != NULL);
+
+	priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
+
+	f_data.cache = priv->cache;
+	f_data.vcal_comp = vcal_comp;
+	f_data.icalcomp = icalcomp;
+
+	icalcomponent_foreach_tzid (icalcomp, add_timezone_cb, &f_data);
+}
 
+/* also removes X-EVOLUTION-CALDAV from all the components */
 static char *
-pack_cobj (ECalBackendCalDAV *cbdav, ECalComponent *ecomp)
+pack_cobj (ECalBackendCalDAV *cbdav, icalcomponent *icomp)
 {
 	ECalBackendCalDAVPrivate *priv;
 	icalcomponent *calcomp;
-	icalcomponent *icomp;
 	char          *objstr;
 
 	priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
 
-	icomp = e_cal_component_get_icalcomponent (ecomp);
-
 	if (icalcomponent_isa (icomp) != ICAL_VCALENDAR_COMPONENT) {
 		icalcomponent *cclone;
 
 		calcomp = e_cal_util_new_top_level ();
+
 		cclone = icalcomponent_new_clone (icomp);
+		strip_x_evolution_caldav (cclone);
 		icalcomponent_add_component (calcomp, cclone);
-		e_cal_util_add_timezones_from_component(calcomp,
-							cclone);
+		add_timezones_from_component (cbdav, calcomp, cclone);
 	} else {
+		icalcomponent *subcomp;
+		icalcomponent_kind my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
+
 		calcomp = icalcomponent_new_clone (icomp);
+		for (subcomp = icalcomponent_get_first_component (calcomp, my_kind);
+		     subcomp;
+		     subcomp = icalcomponent_get_next_component (calcomp, my_kind)) {
+			strip_x_evolution_caldav (subcomp);
+			add_timezones_from_component (cbdav, calcomp, subcomp);
+		}
 	}
 
 	objstr = icalcomponent_as_ical_string_r (calcomp);
@@ -2017,39 +2360,194 @@ sanitize_component (ECalBackend *cb, ECalComponent *comp)
 	e_cal_component_abort_sequence (comp);
 }
 
+static gboolean
+cache_contains (ECalBackendCalDAV *cbdav, const char *uid, const char *rid)
+{
+	ECalBackendCalDAVPrivate *priv;
+	gboolean res;
+	ECalComponent *comp;
+
+	g_return_val_if_fail (cbdav != NULL, FALSE);
+	g_return_val_if_fail (uid != NULL, FALSE);
+
+	priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
+	g_return_val_if_fail (priv != NULL && priv->cache != NULL, FALSE);
+
+	comp = e_cal_backend_cache_get_component (priv->cache, uid, rid);
+	res = comp != NULL;
+
+	if (comp)
+		g_object_unref (comp);
+
+	return res;
+}
+
+/* Returns subcomponent of icalcomp, which is a master object, or icalcomp itself, if it's not a VCALENDAR;
+   Do not free returned pointer, it'll be freed together with the icalcomp.
+*/
+static icalcomponent *
+get_master_comp (ECalBackendCalDAV *cbdav, icalcomponent *icalcomp)
+{
+	icalcomponent *master = icalcomp;
+
+	g_return_val_if_fail (cbdav != NULL, NULL);
+	g_return_val_if_fail (icalcomp != NULL, NULL);
+
+	if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
+		icalcomponent *subcomp;
+		icalcomponent_kind my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
+
+		master = NULL;
+
+		for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
+		     subcomp;
+		     subcomp = icalcomponent_get_next_component (icalcomp, my_kind)) {
+			struct icaltimetype sub_rid = icalcomponent_get_recurrenceid (subcomp);
+
+			if (icaltime_is_null_time (sub_rid)) {
+				master = subcomp;
+				break;
+			}
+		}
+	}
+
+	return master;
+}
+
+static gboolean
+remove_instance (ECalBackendCalDAV *cbdav, icalcomponent *icalcomp, struct icaltimetype rid, CalObjModType mod, gboolean also_exdate)
+{
+	icalcomponent *master = icalcomp;
+	gboolean res = FALSE;
+
+	g_return_val_if_fail (icalcomp != NULL, res);
+	g_return_val_if_fail (!icaltime_is_null_time (rid), res);
+
+	/* remove an instance only */
+	if (icalcomponent_isa (icalcomp) == ICAL_VCALENDAR_COMPONENT) {
+		icalcomponent *subcomp;
+		icalcomponent_kind my_kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbdav));
+		int left = 0;
+		gboolean start_first = FALSE;
+
+		master = NULL;
+
+		/* remove old instance first */
+		for (subcomp = icalcomponent_get_first_component (icalcomp, my_kind);
+		     subcomp;
+		     subcomp = start_first ? icalcomponent_get_first_component (icalcomp, my_kind) : icalcomponent_get_next_component (icalcomp, my_kind)) {
+			struct icaltimetype sub_rid = icalcomponent_get_recurrenceid (subcomp);
+
+			start_first = FALSE;
+
+			if (icaltime_is_null_time (sub_rid)) {
+				master = subcomp;
+				left++;
+			} else if (icaltime_compare (sub_rid, rid) == 0) {
+				icalcomponent_remove_component (icalcomp, subcomp);
+				icalcomponent_free (subcomp);
+				if (master) {
+					break;
+				} else {
+					/* either no master or master not as the first component, thus rescan */
+					left = 0;
+					start_first = TRUE;
+				}
+			} else {
+				left++;
+			}
+		}
+
+		/* whether left at least one instance or a master object */
+		res = left > 0;
+	} else {
+		res = TRUE;
+	}
+
+	if (master && also_exdate) {
+		e_cal_util_remove_instances (master, rid, mod);
+	}
+
+	return res;
+}
+
+static icalcomponent *
+replace_master (ECalBackendCalDAV *cbdav, icalcomponent *old_comp, icalcomponent *new_master)
+{
+	icalcomponent *old_master;
+	if (icalcomponent_isa (old_comp) != ICAL_VCALENDAR_COMPONENT) {
+		icalcomponent_free (old_comp);
+		return new_master;
+	}
+
+	old_master = get_master_comp (cbdav, old_comp);
+	if (!old_master) {
+		/* no master, strange */
+		icalcomponent_free (new_master);
+	} else {
+		icalcomponent_remove_component (old_comp, old_master);
+		icalcomponent_free (old_master);
+
+		icalcomponent_add_component (old_comp, new_master);
+	}
+
+	return old_comp;
+}
+
+/* priv->lock is supposed to be locked already, when calling this function */
 static ECalBackendSyncStatus
-caldav_create_object (ECalBackendSync  *backend,
-		      EDataCal         *cal,
-		      char            **calobj,
-		      char            **uid)
+do_create_object (ECalBackendCalDAV *cbdav, char **calobj, char **uid)
 {
-	ECalBackendCalDAV        *cbdav;
 	ECalBackendCalDAVPrivate *priv;
 	ECalBackendSyncStatus     status;
 	ECalComponent            *comp;
 	gboolean                  online;
-	char                     *href;
 	struct icaltimetype current;
+	icalcomponent *icalcomp;
+	const char *comp_uid;
 
-	cbdav = E_CAL_BACKEND_CALDAV (backend);
 	priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
 
-	g_mutex_lock (priv->lock);
-
 	status = check_state (cbdav, &online);
 
 	if (status != GNOME_Evolution_Calendar_Success) {
-		g_mutex_unlock (priv->lock);
 		return status;
 	}
 
 	comp = e_cal_component_new_from_string (*calobj);
 
 	if (comp == NULL) {
-		g_mutex_unlock (priv->lock);
 		return GNOME_Evolution_Calendar_InvalidObject;
 	}
 
+	icalcomp = e_cal_component_get_icalcomponent (comp);
+	if (icalcomp == NULL) {
+		g_object_unref (comp);
+		return GNOME_Evolution_Calendar_InvalidObject;
+	}
+
+	comp_uid = icalcomponent_get_uid (icalcomp);
+	if (!comp_uid) {
+		char *new_uid;
+
+		new_uid = e_cal_component_gen_uid ();
+		if (!new_uid) {
+			g_object_unref (comp);
+			return GNOME_Evolution_Calendar_InvalidObject;
+		}
+
+		icalcomponent_set_uid (icalcomp, new_uid);
+		comp_uid = icalcomponent_get_uid (icalcomp);
+
+		g_free (new_uid);
+	}
+
+	/* check the object is not in our cache */
+	if (cache_contains (cbdav, comp_uid, NULL)) {
+		g_object_unref (comp);
+		return GNOME_Evolution_Calendar_ObjectIdAlreadyExists;
+	}
+
 	/* Set the created and last modified times on the component */
 	current = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
 	e_cal_component_set_created (comp, &current);
@@ -2061,74 +2559,75 @@ caldav_create_object (ECalBackendSync  *backend,
 	if (online) {
 		CalDAVObject object;
 
-		href = e_cal_component_gen_href (comp);
-		
-		object.href  = href;
+		object.href  = ecalcomp_gen_href (comp);
 		object.etag  = NULL;
-		object.cdata = pack_cobj (cbdav, comp);
+		object.cdata = pack_cobj (cbdav, icalcomp);
 
-		status = caldav_server_put_object (cbdav, &object);
-		if (status == GNOME_Evolution_Calendar_Success) {
-			e_cal_component_set_href (comp, object.href);
-			e_cal_component_set_etag (comp, object.etag);
-		}
+		status = caldav_server_put_object (cbdav, &object, icalcomp);
 
 		caldav_object_free (&object, FALSE);
 	} else {
 		/* mark component as out of synch */
-		e_cal_component_set_synch_state (comp,
-				E_CAL_COMPONENT_LOCALLY_CREATED);
+		/*ecalcomp_set_synch_state (comp, ECALCOMP_LOCALLY_CREATED); */
 	}
 
-	if (status != GNOME_Evolution_Calendar_Success) {
-		g_object_unref (comp);
-		g_mutex_unlock (priv->lock);
-		return status;
-	}
+	if (status == GNOME_Evolution_Calendar_Success) {
+		if (uid)
+			*uid = g_strdup (comp_uid);
 
-	/* We should prolly check for cache errors
-	 * but when that happens we are kinda hosed anyway */
-	e_cal_backend_cache_put_component (priv->cache, comp);
-	*calobj = e_cal_component_get_as_string (comp);
+		icalcomp = get_comp_from_cache (cbdav, comp_uid, NULL, NULL, NULL);
 
-	g_mutex_unlock (priv->lock);
+		if (icalcomp) {
+			icalcomponent *master = get_master_comp (cbdav, icalcomp);
+
+			if (!master)
+				*calobj = e_cal_component_get_as_string (comp);
+			else
+				*calobj = icalcomponent_as_ical_string_r (master);
+
+			icalcomponent_free (icalcomp);
+		} else {
+			*calobj = e_cal_component_get_as_string (comp);
+		}
+	}
+
+	g_object_unref (comp);
 
 	return status;
 }
 
+/* priv->lock is supposed to be locked already, when calling this function */
 static ECalBackendSyncStatus
-caldav_modify_object (ECalBackendSync  *backend,
-		      EDataCal         *cal,
-		      const char       *calobj,
-		      CalObjModType     mod,
-		      char            **old_object,
-		      char            **new_object)
+do_modify_object (ECalBackendCalDAV *cbdav, const char *calobj, CalObjModType mod, char **old_object, char **new_object)
 {
-	ECalBackendCalDAV        *cbdav;
 	ECalBackendCalDAVPrivate *priv;
 	ECalBackendSyncStatus     status;
 	ECalComponent            *comp;
-	ECalComponent            *cache_comp;
+	icalcomponent            *cache_comp;
 	gboolean                  online;
-	const char		 *uid = NULL;
+	ECalComponentId		 *id;
 	struct icaltimetype current;
+	char *href = NULL, *etag = NULL;
 
-	cbdav = E_CAL_BACKEND_CALDAV (backend);
 	priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
 
-	g_mutex_lock (priv->lock);
+	if (new_object)
+		*new_object = NULL;
 
 	status = check_state (cbdav, &online);
-
 	if (status != GNOME_Evolution_Calendar_Success) {
-		g_mutex_unlock (priv->lock);
 		return status;
 	}
 
 	comp = e_cal_component_new_from_string (calobj);
 
 	if (comp == NULL) {
-		g_mutex_unlock (priv->lock);
+		return GNOME_Evolution_Calendar_InvalidObject;
+	}
+
+	if (!e_cal_component_get_icalcomponent (comp) ||
+	    icalcomponent_isa (e_cal_component_get_icalcomponent (comp)) != e_cal_backend_get_kind (E_CAL_BACKEND (cbdav))) {
+		g_object_unref (comp);
 		return GNOME_Evolution_Calendar_InvalidObject;
 	}
 
@@ -2136,138 +2635,271 @@ caldav_modify_object (ECalBackendSync  *backend,
 	current = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
 	e_cal_component_set_last_modified (comp, &current);
 
-	/* sanitize the component*/
+	/* sanitize the component */
 	sanitize_component ((ECalBackend *)cbdav, comp);
 
-	e_cal_component_get_uid (comp, &uid);
+	id = e_cal_component_get_id (comp);
+	g_return_val_if_fail (id != NULL, GNOME_Evolution_Calendar_OtherError);
+
+	/* fetch full component from cache, it will be pushed to the server */
+	cache_comp = get_comp_from_cache (cbdav, id->uid, NULL, &href, &etag);
 
-	cache_comp = e_cal_backend_cache_get_component (priv->cache, uid, NULL);
 	if (cache_comp == NULL) {
-		g_mutex_unlock (priv->lock);
+		e_cal_component_free_id (id);
+		g_object_unref (comp);
+		g_free (href);
+		g_free (etag);
 		return GNOME_Evolution_Calendar_ObjectNotFound;
 	}
 
+	if (!online) {
+		/* mark component as out of synch */
+		/*ecalcomp_set_synch_state (comp, ECALCOMP_LOCALLY_MODIFIED);*/
+	}
+
+	if (old_object) {
+		*old_object = NULL;
+
+		if (e_cal_component_is_instance (comp)) {
+			/* set detached instance as the old object, if any */
+			ECalComponent *old_instance = e_cal_backend_cache_get_component (priv->cache, id->uid, id->rid);
+
+			if (old_instance) {
+				*old_object = e_cal_component_get_as_string (old_instance);
+				g_object_unref (old_instance);
+			}
+		} 
+
+		if (!*old_object) {
+			icalcomponent *master = get_master_comp (cbdav, cache_comp);
+
+			if (master) {
+				/* set full component as the old object */
+				*old_object = icalcomponent_as_ical_string_r (master);
+			}
+		}
+	}
+
+	switch (mod) {
+	case CALOBJ_MOD_THIS:
+		if (e_cal_component_is_instance (comp)) {
+			icalcomponent *new_comp = e_cal_component_get_icalcomponent (comp);
+
+			/* new object is only this instance */
+			if (new_object)
+				*new_object = e_cal_component_get_as_string (comp);
+
+			/* add the detached instance */
+			if (icalcomponent_isa (cache_comp) == ICAL_VCALENDAR_COMPONENT) {
+				/* do not modify the EXDATE, as the component will be put back */
+				remove_instance (cbdav, cache_comp, icalcomponent_get_recurrenceid (new_comp), mod, FALSE);
+			} else {
+				/* this is only a master object, thus make is a VCALENDAR component */
+				icalcomponent *icomp;
+
+				icomp = e_cal_util_new_top_level();
+				icalcomponent_add_component (icomp, cache_comp);
+
+				/* no need to free the cache_comp, as it is inside icomp */
+				cache_comp = icomp;
+			}
+
+			/* add the detached instance finally */
+			icalcomponent_add_component (cache_comp, icalcomponent_new_clone (new_comp));
+		} else {
+			cache_comp = replace_master (cbdav, cache_comp, icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp)));
+		}
+		break;
+	case CALOBJ_MOD_ALL:
+		cache_comp = replace_master (cbdav, cache_comp, icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp)));
+		break;
+	case CALOBJ_MOD_THISANDPRIOR:
+	case CALOBJ_MOD_THISANDFUTURE:
+		break;
+	}
+
 	if (online) {
 		CalDAVObject object;
 
-		object.href  = e_cal_component_get_href (cache_comp);
-		object.etag  = e_cal_component_get_etag (cache_comp);
-		object.cdata = pack_cobj (cbdav, comp);
+		object.href  = href;
+		object.etag  = etag;
+		object.cdata = pack_cobj (cbdav, cache_comp);
 
-		status = caldav_server_put_object (cbdav, &object);
-		if (status == GNOME_Evolution_Calendar_Success) {
-			e_cal_component_set_href (comp, object.href);
-			e_cal_component_set_etag (comp, object.etag);
-		}
+		status = caldav_server_put_object (cbdav, &object, cache_comp);
 
 		caldav_object_free (&object, FALSE);
+		href = NULL;
+		etag = NULL;
 	} else {
 		/* mark component as out of synch */
-		e_cal_component_set_synch_state (comp,
-				E_CAL_COMPONENT_LOCALLY_MODIFIED);
+		/*ecalcomp_set_synch_state (comp, ECALCOMP_LOCALLY_MODIFIED);*/
 	}
 
-	if (status != GNOME_Evolution_Calendar_Success) {
-		g_object_unref (comp);
-		g_mutex_unlock (priv->lock);
-		return status;
-	}
+	if (status == GNOME_Evolution_Calendar_Success) {
+		if (new_object && !*new_object) {
+			icalcomponent *master = get_master_comp (cbdav, cache_comp);
 
-	/* We should prolly check for cache errors
-	 * but when that happens we are kinda hosed anyway */
-	e_cal_backend_cache_put_component (priv->cache, comp);
-	*old_object = e_cal_component_get_as_string (cache_comp);
-	*new_object = e_cal_component_get_as_string (comp);
+			if (master)
+				*new_object = icalcomponent_as_ical_string_r (master);
+		}
+	}
 
-	g_mutex_unlock (priv->lock);
+	e_cal_component_free_id (id);
+	icalcomponent_free (cache_comp);
+	g_object_unref (comp);
+	g_free (href);
+	g_free (etag);
 
 	return status;
 }
 
+/* priv->lock is supposed to be locked already, when calling this function */
 static ECalBackendSyncStatus
-caldav_remove_object (ECalBackendSync  *backend,
-		      EDataCal         *cal,
-		      const char       *uid,
-		      const char       *rid,
-		      CalObjModType     mod,
-		      char            **old_object,
-		      char            **object)
+do_remove_object (ECalBackendCalDAV *cbdav, const char *uid, const char *rid, CalObjModType mod, char **old_object, char **object)
 {
-	ECalBackendCalDAV        *cbdav;
 	ECalBackendCalDAVPrivate *priv;
 	ECalBackendSyncStatus     status;
-	ECalComponent            *cache_comp;
+	icalcomponent            *cache_comp;
 	gboolean                  online;
+	char *href = NULL, *etag = NULL;
 
-	cbdav = E_CAL_BACKEND_CALDAV (backend);
 	priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
 
-	g_mutex_lock (priv->lock);
+	if (object)
+		*object = NULL;
 
 	status = check_state (cbdav, &online);
-
 	if (status != GNOME_Evolution_Calendar_Success) {
-		g_mutex_unlock (priv->lock);
 		return status;
 	}
 
-	cache_comp = e_cal_backend_cache_get_component (priv->cache, uid, rid);
-
-	if (cache_comp == NULL && rid && *rid) {
-		/* we do not have this instance in cache directly, thus try to get master object */
-		cache_comp = e_cal_backend_cache_get_component (priv->cache, uid, "");
-	}
+	cache_comp = get_comp_from_cache (cbdav, uid, NULL, &href, &etag);
 
 	if (cache_comp == NULL) {
-		g_mutex_unlock (priv->lock);
 		return GNOME_Evolution_Calendar_ObjectNotFound;
 	}
 
-	*old_object = e_cal_component_get_as_string (cache_comp);
+	if (old_object) {
+		ECalComponent *old = e_cal_backend_cache_get_component (priv->cache, uid, rid);
 
-	if (mod == CALOBJ_MOD_THIS && rid && *rid)
-		e_cal_util_remove_instances (e_cal_component_get_icalcomponent (cache_comp), icaltime_from_string (rid), mod);
+		if (old) {
+			*old_object = e_cal_component_get_as_string (old);
+			g_object_unref (old);
+		} else {
+			icalcomponent *master = get_master_comp (cbdav, cache_comp);
+
+			if (master)
+				*old_object = icalcomponent_as_ical_string_r (master);
+		}
+	}
+
+	switch (mod) {
+	case CALOBJ_MOD_THIS:
+		if (rid && *rid) {
+			/* remove one instance from the component */
+			if (remove_instance (cbdav, cache_comp, icaltime_from_string (rid), mod, TRUE)) {
+				if (object) {
+					icalcomponent *master = get_master_comp (cbdav, cache_comp);
+
+					if (master)
+						*object = icalcomponent_as_ical_string_r (master);
+				}
+			} else {
+				/* this was the last instance, thus delete whole component */
+				rid = NULL;
+				remove_comp_from_cache (cbdav, uid, NULL);
+			}
+		} else {
+			/* remove whole object */
+			remove_comp_from_cache (cbdav, uid, NULL);
+		}
+		break;
+	case CALOBJ_MOD_ALL:
+		remove_comp_from_cache (cbdav, uid, NULL);
+		break;
+	case CALOBJ_MOD_THISANDPRIOR:
+	case CALOBJ_MOD_THISANDFUTURE:
+		break;
+	}
 
 	if (online) {
 		CalDAVObject caldav_object;
 
-		caldav_object.href  = e_cal_component_get_href (cache_comp);
-		caldav_object.etag  = e_cal_component_get_etag (cache_comp);
+		caldav_object.href  = href;
+		caldav_object.etag  = etag;
 		caldav_object.cdata = NULL;
 
 		if (mod == CALOBJ_MOD_THIS && rid && *rid) {
 			caldav_object.cdata = pack_cobj (cbdav, cache_comp);
 
-			status = caldav_server_put_object (cbdav, &caldav_object);
-			if (status == GNOME_Evolution_Calendar_Success) {
-				e_cal_component_set_href (cache_comp, caldav_object.href);
-				e_cal_component_set_etag (cache_comp, caldav_object.etag);
-			}
+			status = caldav_server_put_object (cbdav, &caldav_object, cache_comp);
 		} else
 			status = caldav_server_delete_object (cbdav, &caldav_object);
 
 		caldav_object_free (&caldav_object, FALSE);
+		href = NULL;
+		etag = NULL;
 	} else {
 		/* mark component as out of synch */
-		if (mod == CALOBJ_MOD_THIS && rid && *rid)
-			e_cal_component_set_synch_state (cache_comp, E_CAL_COMPONENT_LOCALLY_MODIFIED);
+		/*if (mod == CALOBJ_MOD_THIS && rid && *rid)
+			ecalcomp_set_synch_state (cache_comp_master, ECALCOMP_LOCALLY_MODIFIED);
 		else
-			e_cal_component_set_synch_state (cache_comp, E_CAL_COMPONENT_LOCALLY_DELETED);
+			ecalcomp_set_synch_state (cache_comp_master, ECALCOMP_LOCALLY_DELETED);*/
 	}
 
-	if (status != GNOME_Evolution_Calendar_Success) {
-		g_mutex_unlock (priv->lock);
-		return status;
-	}
+	icalcomponent_free (cache_comp);
+	g_free (href);
+	g_free (etag);
 
-	/* We should prolly check for cache errors
-	 * but when that happens we are kinda hosed anyway */
-	if (mod == CALOBJ_MOD_THIS && rid && *rid) {
-		e_cal_backend_cache_put_component (priv->cache, cache_comp);
-		*object = e_cal_component_get_as_string (cache_comp);
-	} else
-		e_cal_backend_cache_remove_component (priv->cache, uid, rid);
+	return status;
+}
+
+static ECalBackendSyncStatus
+caldav_create_object (ECalBackendSync *backend, EDataCal *cal, char **calobj, char **uid)
+{
+	ECalBackendCalDAV        *cbdav;
+	ECalBackendCalDAVPrivate *priv;
+	ECalBackendSyncStatus     status;
+
+	cbdav = E_CAL_BACKEND_CALDAV (backend);
+	priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
 
+	g_mutex_lock (priv->lock);
+	status = do_create_object (cbdav, calobj, uid);
+	g_mutex_unlock (priv->lock);
+
+	return status;
+}
+
+static ECalBackendSyncStatus
+caldav_modify_object (ECalBackendSync *backend, EDataCal *cal, const char *calobj, CalObjModType mod, char **old_object, char **new_object)
+{
+	ECalBackendCalDAV        *cbdav;
+	ECalBackendCalDAVPrivate *priv;
+	ECalBackendSyncStatus     status;
+
+	cbdav = E_CAL_BACKEND_CALDAV (backend);
+	priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
+
+	g_mutex_lock (priv->lock);
+	status = do_modify_object (cbdav, calobj, mod, old_object, new_object);
+	g_mutex_unlock (priv->lock);
+
+	return status;
+}
+
+static ECalBackendSyncStatus
+caldav_remove_object (ECalBackendSync *backend, EDataCal *cal, const char *uid, const char *rid, CalObjModType mod, char **old_object, char **object)
+{
+	ECalBackendCalDAV        *cbdav;
+	ECalBackendCalDAVPrivate *priv;
+	ECalBackendSyncStatus     status;
+
+	cbdav = E_CAL_BACKEND_CALDAV (backend);
+	priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
+
+	g_mutex_lock (priv->lock);
+	status = do_remove_object (cbdav, uid, rid, mod, old_object, object);
 	g_mutex_unlock (priv->lock);
 
 	return status;
@@ -2282,7 +2914,6 @@ caldav_discard_alarm (ECalBackendSync *backend,
 	return GNOME_Evolution_Calendar_Success;
 }
 
-/* FIXME: use list here? */
 static ECalBackendSyncStatus
 extract_objects (icalcomponent       *icomp,
 		 icalcomponent_kind   ekind,
@@ -2320,6 +2951,37 @@ extract_objects (icalcomponent       *icomp,
 	return GNOME_Evolution_Calendar_Success;
 }
 
+static gboolean
+extract_timezones (ECalBackendCalDAV *cbdav, icalcomponent *icomp)
+{
+	ECalBackendCalDAVPrivate *priv;
+	GList *timezones = NULL, *iter;
+	icaltimezone *zone;
+	
+	g_return_val_if_fail (cbdav != NULL, FALSE);
+	g_return_val_if_fail (icomp != NULL, FALSE);
+
+	if (extract_objects (icomp, ICAL_VTIMEZONE_COMPONENT, &timezones) != GNOME_Evolution_Calendar_Success) {
+		return FALSE;
+	}
+
+	priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
+
+	zone = icaltimezone_new ();
+	for (iter = timezones; iter; iter = iter->next) {
+		if (icaltimezone_set_component (zone, iter->data)) {
+			e_cal_backend_cache_put_timezone (priv->cache, zone);
+		} else {
+			icalcomponent_free (iter->data);
+		}
+	}
+
+	icaltimezone_free (zone, TRUE);
+	g_list_free (timezones);
+
+	return TRUE;
+}
+
 #define is_error(__status) (__status != GNOME_Evolution_Calendar_Success)
 
 static ECalBackendSyncStatus
@@ -2331,179 +2993,102 @@ process_object (ECalBackendCalDAV   *cbdav,
 	ECalBackendCalDAVPrivate *priv;
 	ECalBackendSyncStatus     status;
 	ECalBackend              *backend;
-	ECalComponent            *ccomp;
 	struct icaltimetype       now;
-	ECalComponentId          *id;
-	const char               *uid;
-	char                     *rid;
-	char                     *ostr;
-	char                     *oostr;
-	gboolean                  is_declined;
+	char *new_obj_str;
+	gboolean is_declined, is_in_cache;
+	CalObjModType mod;
+	ECalComponentId *id = e_cal_component_get_id (ecomp);
 
 	priv = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
 	backend = E_CAL_BACKEND (cbdav);
 
+	g_return_val_if_fail (id != NULL, GNOME_Evolution_Calendar_InvalidObject);
+
 	/* ctime, mtime */
 	now = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
 	e_cal_component_set_created (ecomp, &now);
 	e_cal_component_set_last_modified (ecomp, &now);
 
-	e_cal_component_get_uid (ecomp, &uid);
-	rid = e_cal_component_get_recurid_as_string (ecomp);
-
-	ccomp = e_cal_backend_cache_get_component (priv->cache, uid, NULL);
-
-	if (ccomp != NULL) {
-		oostr = e_cal_component_get_as_string (ccomp);
-	} else {
-		oostr = NULL;
-	}
-
-	ostr = e_cal_component_get_as_string (ecomp);
+	/* just to check whether component exists in a cache */
+	is_in_cache = cache_contains (cbdav, id->uid, NULL) || cache_contains (cbdav, id->uid, id->rid);
 
+	new_obj_str = e_cal_component_get_as_string (ecomp);
+	mod = e_cal_component_is_instance (ecomp) ? CALOBJ_MOD_THIS : CALOBJ_MOD_ALL;
 	status = GNOME_Evolution_Calendar_Success;
 
 	switch (method) {
-
-		case ICAL_METHOD_PUBLISH:
-		case ICAL_METHOD_REQUEST:
-		case ICAL_METHOD_REPLY:
-
+	case ICAL_METHOD_PUBLISH:
+	case ICAL_METHOD_REQUEST:
+	case ICAL_METHOD_REPLY:
 		is_declined = e_cal_backend_user_declined (e_cal_component_get_icalcomponent (ecomp));
-		if (online) {
-			CalDAVObject object = { NULL, };
-
-			if (ccomp) {
-				char *href;
-				char *etag;
-
-				href = e_cal_component_get_href (ccomp);
-				etag = e_cal_component_get_etag (ccomp);
-
-				object.href  = href;
-				object.etag  = etag;
-
-			} else if (!is_declined) {
-				object.href = e_cal_component_gen_href (ecomp);
-			}
-
-			if (!is_declined || ccomp) {
-				if (!is_declined) {
-					object.cdata = pack_cobj (cbdav, ecomp);
-					status = caldav_server_put_object (cbdav, &object);
+		if (is_in_cache) {
+			if (!is_declined) {
+				char *new_object = NULL, *old_object = NULL;
+
+				status = do_modify_object (cbdav, new_obj_str, mod, &old_object, &new_object);
+				if (status == GNOME_Evolution_Calendar_Success) {
+					if (!old_object)
+						e_cal_backend_notify_object_created (backend, new_object);
+					else
+						e_cal_backend_notify_object_modified (backend, old_object, new_object);
+				}
 
-					if (status == GNOME_Evolution_Calendar_Success) {
-						e_cal_component_set_href (ecomp, object.href);
-						e_cal_component_set_etag (ecomp, object.etag);
+				g_free (new_object);
+				g_free (old_object);
+			} else {
+				char *new_object = NULL, *old_object = NULL;
+
+				status = do_remove_object (cbdav, id->uid, id->rid, mod, &old_object, &new_object);
+				if (status == GNOME_Evolution_Calendar_Success) {
+					if (new_object) {
+						e_cal_backend_notify_object_modified (backend, old_object, new_object);
+					} else {
+						e_cal_backend_notify_object_removed (backend, id, old_object, NULL);
 					}
-				} else {
-					object.cdata = NULL;
-					status = caldav_server_delete_object (cbdav, &object);
 				}
-				caldav_object_free (&object, FALSE);
-			}
-		} else {
-			ECalComponentSyncState sstate = E_CAL_COMPONENT_IN_SYNCH;
-
-			if (ccomp) {
-				if (!is_declined)
-					sstate = E_CAL_COMPONENT_LOCALLY_MODIFIED;
-				else
-					sstate = E_CAL_COMPONENT_LOCALLY_DELETED;
-			} else if (!is_declined) {
-				sstate = E_CAL_COMPONENT_LOCALLY_CREATED;
-			}
-
-			e_cal_component_set_synch_state (ecomp, sstate);
 
-		}
-
-		if (status != GNOME_Evolution_Calendar_Success) {
-			break;
-		}
-
-		if (!is_declined)
-			e_cal_backend_cache_put_component (priv->cache, ecomp);
-		else
-			e_cal_backend_cache_remove_component (priv->cache, uid, rid);
-
-		if (ccomp) {
-			if (!is_declined)
-				e_cal_backend_notify_object_modified (backend, ostr, oostr);
-			else {
-				id = e_cal_component_get_id (ccomp);
-				e_cal_backend_notify_object_removed (E_CAL_BACKEND (backend), id, oostr, NULL);
-				e_cal_component_free_id (id);
+				g_free (new_object);
+				g_free (old_object);
 			}
 		} else if (!is_declined) {
-			e_cal_backend_notify_object_created (backend, ostr);
-		}
-
-		break;
-
-
-		case ICAL_METHOD_CANCEL:
+			char *new_object = new_obj_str;
 
-			if (ccomp == NULL) {
-				status = GNOME_Evolution_Calendar_ObjectNotFound;
-				break;
+			status = do_create_object (cbdav, &new_object, NULL);
+			if (status == GNOME_Evolution_Calendar_Success) {
+				e_cal_backend_notify_object_created (backend, new_object);
 			}
 
-			/* FIXME: this is not working for instances
-			 * of recurring appointments - yet - */
-			if (online) {
-				CalDAVObject object;
-				char *href;
-				char *etag;
-
-				href = e_cal_component_get_href (ccomp);
-				etag = e_cal_component_get_etag (ccomp);
-
-				object.href  = href;
-				object.etag  = etag;
-				object.cdata = NULL;
-
-				status = caldav_server_delete_object (cbdav,
-								      &object);
-
-				caldav_object_free (&object, FALSE);
-
-			} else {
-				/* mark component as out of synch */
-				e_cal_component_set_synch_state (ecomp,
-				E_CAL_COMPONENT_LOCALLY_DELETED);
-
-			}
+			if (new_object != new_obj_str)
+				g_free (new_object);
+		}
+		break;
+	case ICAL_METHOD_CANCEL:
+		if (is_in_cache) {
+			char *old_object = NULL, *new_object = NULL;
 
-			if (status != GNOME_Evolution_Calendar_Success) {
-				break;
+			status = do_remove_object (cbdav, id->uid, id->rid, CALOBJ_MOD_THIS, &old_object, &new_object);
+			if (status == GNOME_Evolution_Calendar_Success) {
+				if (new_object) {
+					e_cal_backend_notify_object_modified (backend, old_object, new_object);
+				} else {
+					e_cal_backend_notify_object_removed (backend, id, old_object, NULL);
+				}
 			}
 
-			e_cal_backend_cache_remove_component (priv->cache,
-							      uid,
-							      rid);
-
-			id = e_cal_component_get_id (ccomp);
-			e_cal_backend_notify_object_removed (E_CAL_BACKEND (backend),
-							     id,
-							     oostr,
-							     ostr);
-			e_cal_component_free_id (id);
-			break;
+			g_free (old_object);
+			g_free (new_object);
+		} else {
+			status = GNOME_Evolution_Calendar_ObjectNotFound;
+		}
+		break;
 
-		default:
-			/* WTF ? */
-			status = GNOME_Evolution_Calendar_UnsupportedMethod;
-			break;
+	default:
+		status = GNOME_Evolution_Calendar_UnsupportedMethod;
+		break;
 	}
 
-	g_free (ostr);
-	g_free (oostr);
-	g_free (rid);
-
-	if (ccomp) {
-		g_object_unref (ccomp);
-	}
+	e_cal_component_free_id (id);
+	g_free (new_obj_str);
 
 	return status;
 }
@@ -2520,7 +3105,6 @@ caldav_receive_objects (ECalBackendSync *backend,
 	icalcomponent_kind        kind;
 	icalproperty_method       tmethod;
 	gboolean                  online;
-	GList                    *timezones = NULL;
 	GList                    *objects;
 	GList                    *iter;
 
@@ -2538,25 +3122,12 @@ caldav_receive_objects (ECalBackendSync *backend,
 	status = extract_objects (icomp, kind, &objects);
 
 	if (status != GNOME_Evolution_Calendar_Success) {
+		icalcomponent_free (icomp);
 		return status;
 	}
 
 	/* Extract optional timezone compnents */
-	kind = ICAL_VTIMEZONE_COMPONENT;
-	status = extract_objects (icomp, kind, &timezones);
-
-	if (status == GNOME_Evolution_Calendar_Success) {
-		for (iter = timezones; iter; iter = iter->next) {
-			icaltimezone *zone = icaltimezone_new ();
-
-			if (icaltimezone_set_component (zone, iter->data))
-				e_cal_backend_cache_put_timezone (priv->cache, zone);
-			else
-				icalcomponent_free (iter->data);
-
-			icaltimezone_free (zone, TRUE);
-		}
-	}
+	extract_timezones (cbdav, icomp);
 
 	/*   */
 	g_mutex_lock (priv->lock);
@@ -2566,6 +3137,7 @@ caldav_receive_objects (ECalBackendSync *backend,
 	if (status != GNOME_Evolution_Calendar_Success) {
 		/* FIXME: free components here */
 		g_mutex_unlock (priv->lock);
+		icalcomponent_free (icomp);
 		return status;
 	}
 
@@ -2581,9 +3153,7 @@ caldav_receive_objects (ECalBackendSync *backend,
 
 		e_cal_component_set_icalcomponent (ecomp, scomp);
 
-		if (icalcomponent_get_first_property (scomp,
-						      ICAL_METHOD_PROPERTY)) {
-
+		if (icalcomponent_get_first_property (scomp, ICAL_METHOD_PROPERTY)) {
 			method = icalcomponent_get_method (scomp);
 		} else {
 			method = tmethod;
@@ -2595,9 +3165,9 @@ caldav_receive_objects (ECalBackendSync *backend,
 	}
 
 	g_list_free (objects);
-	g_list_free (timezones);
 
 	g_mutex_unlock (priv->lock);
+	icalcomponent_free (icomp);
 
 	return status;
 }
@@ -2654,22 +3224,24 @@ caldav_get_object (ECalBackendSync  *backend,
 {
 	ECalBackendCalDAV        *cbdav;
 	ECalBackendCalDAVPrivate *priv;
-	ECalComponent            *comp;
+	icalcomponent            *icalcomp;
 
 	cbdav = E_CAL_BACKEND_CALDAV (backend);
 	priv  = E_CAL_BACKEND_CALDAV_GET_PRIVATE (cbdav);
 
 	g_mutex_lock (priv->lock);
-	comp = e_cal_backend_cache_get_component (priv->cache, uid, rid);
+
+	*object = NULL;
+	icalcomp = get_comp_from_cache (cbdav, uid, rid, NULL, NULL);
+
 	g_mutex_unlock (priv->lock);
 
-	if (comp == NULL) {
-		*object = NULL;
+	if (!icalcomp) {
 		return GNOME_Evolution_Calendar_ObjectNotFound;
 	}
 
-	*object = e_cal_component_get_as_string (comp);
-	g_object_unref (comp);
+	*object = icalcomponent_as_ical_string_r (icalcomp);
+	icalcomponent_free (icalcomp);
 
 	return GNOME_Evolution_Calendar_Success;
 }
@@ -2806,7 +3378,7 @@ caldav_get_object_list (ECalBackendSync  *backend,
 		return GNOME_Evolution_Calendar_InvalidQuery;
 	}
 
-	if (g_str_equal (sexp, "#t")) {
+	if (g_str_equal (sexp_string, "#t")) {
 		do_search = FALSE;
 	} else {
 		do_search = TRUE;
@@ -2860,7 +3432,7 @@ caldav_start_query (ECalBackend  *backend,
 
 	/* FIXME:check invalid sexp */
 
-	if (g_str_equal (sexp, "#t")) {
+	if (g_str_equal (sexp_string, "#t")) {
 		do_search = FALSE;
 	} else {
 		do_search = TRUE;
@@ -3191,4 +3763,3 @@ e_cal_backend_caldav_class_init (ECalBackendCalDAVClass *class)
 	backend_class->internal_get_default_timezone = caldav_internal_get_default_timezone;
 	backend_class->internal_get_timezone         = caldav_internal_get_timezone;
 }
-
diff --git a/calendar/libedata-cal/e-cal-backend-util.c b/calendar/libedata-cal/e-cal-backend-util.c
index 7bf9158..cd38812 100644
--- a/calendar/libedata-cal/e-cal-backend-util.c
+++ b/calendar/libedata-cal/e-cal-backend-util.c
@@ -170,7 +170,7 @@ is_attendee_declined (icalcomponent *icalcomp, const char *email)
 	for (prop = icalcomponent_get_first_property (icalcomp, ICAL_ATTENDEE_PROPERTY);
 	     prop != NULL;
 	     prop = icalcomponent_get_next_property (icalcomp, ICAL_ATTENDEE_PROPERTY)) {
-		const char *attendee;
+		char *attendee;
 		char *text = NULL;
 
 		attendee = icalproperty_get_value_as_string_r (prop);
@@ -183,9 +183,11 @@ is_attendee_declined (icalcomponent *icalcomp, const char *email)
 
 		if (!g_ascii_strcasecmp (email, text)) {
 			g_free (text);
+			g_free (attendee);
 			break;
 		}
 		g_free (text);
+		g_free (attendee);
 	}
 
 	if (!prop)



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]