ecb_ews_get_changes_sync (ECalMetaBackend *meta_backend,
			  const gchar *last_sync_tag,
			  gboolean is_repeat,
			  gchar **out_new_sync_tag,
			  gboolean *out_repeat,
			  GSList **out_created_objects,
			  GSList **out_modified_objects,
			  GSList **out_removed_objects,
			  GCancellable *cancellable,
			  GError **error)
{
	ECalBackendEws *cbews;
	ECalCache *cal_cache;
	gboolean success = TRUE;
	GError *local_error = NULL;

	g_return_val_if_fail (E_IS_CAL_BACKEND_EWS (meta_backend), FALSE);
	g_return_val_if_fail (out_new_sync_tag != NULL, FALSE);
	g_return_val_if_fail (out_repeat != NULL, FALSE);
	g_return_val_if_fail (out_created_objects != NULL, FALSE);
	g_return_val_if_fail (out_modified_objects != NULL, FALSE);
	g_return_val_if_fail (out_removed_objects != NULL, FALSE);

	*out_created_objects = NULL;
	*out_modified_objects = NULL;
	*out_removed_objects = NULL;

	cbews = E_CAL_BACKEND_EWS (meta_backend);

	cal_cache = e_cal_meta_backend_ref_cache (meta_backend);
	g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE);

	g_rec_mutex_lock (&cbews->priv->cnc_lock);

	if (cbews->priv->is_freebusy_calendar) {
		ESourceEwsFolder *ews_folder;
		EEWSFreeBusyData fbdata;
		GSList *free_busy = NULL, *link;
		gboolean success;
		time_t today;

		ews_folder = e_source_get_extension (e_backend_get_source (E_BACKEND (cbews)), E_SOURCE_EXTENSION_EWS_FOLDER);

		today = time_day_begin (time (NULL));

		fbdata.period_start = time_add_week (today, -e_source_ews_folder_get_freebusy_weeks_before (ews_folder));
		fbdata.period_end = time_day_end (time_add_week (today, e_source_ews_folder_get_freebusy_weeks_after (ews_folder)));
		fbdata.user_mails = g_slist_prepend (NULL, e_source_ews_folder_dup_foreign_mail (ews_folder));

		success = e_ews_connection_get_free_busy_sync (cbews->priv->cnc, G_PRIORITY_DEFAULT,
			e_ews_cal_utils_prepare_free_busy_request, &fbdata,
			&free_busy, cancellable, &local_error);

		if (success) {
			icaltimezone *utc_zone = icaltimezone_get_utc_timezone ();
			GSList *comps = NULL;
			GHashTable *known;
			GHashTableIter iter;
			gpointer key;

			known = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);

			if (e_cal_cache_search_components (cal_cache, NULL, &comps, cancellable, NULL)) {
				for (link = comps; link; link = g_slist_next (link)) {
					ECalComponent *comp = link->data;
					icalcomponent *icomp;
					const gchar *uid;

					if (!comp)
						continue;

					icomp = e_cal_component_get_icalcomponent (comp);
					if (!icomp)
						continue;

					uid = icalcomponent_get_uid (icomp);

					if (uid && *uid)
						g_hash_table_insert (known, g_strdup (uid), g_object_ref (comp));
				}

				g_slist_free_full (comps, g_object_unref);
			}

			for (link = free_busy; link; link = g_slist_next (link)) {
				icalcomponent *fbcomp = link->data;
				icalproperty *fbprop;
				icalparameter *param;
				struct icalperiodtype fb;
				icalparameter_fbtype fbtype;

				if (!fbcomp || icalcomponent_isa (fbcomp) != ICAL_VFREEBUSY_COMPONENT)
					continue;

				for (fbprop = icalcomponent_get_first_property (fbcomp, ICAL_FREEBUSY_PROPERTY);
				     fbprop;
				     fbprop = icalcomponent_get_next_property (fbcomp, ICAL_FREEBUSY_PROPERTY)) {
					ECalComponent *ecomp;
					icalcomponent *vevent;
					const gchar *id, *summary, *location;

					param = icalproperty_get_first_parameter (fbprop, ICAL_FBTYPE_PARAMETER);
					if (!param)
						continue;

					fbtype = icalparameter_get_fbtype (param);

					if (fbtype != ICAL_FBTYPE_FREE &&
					    fbtype != ICAL_FBTYPE_BUSY &&
					    fbtype != ICAL_FBTYPE_BUSYUNAVAILABLE &&
					    fbtype != ICAL_FBTYPE_BUSYTENTATIVE)
						continue;

					fb = icalproperty_get_freebusy (fbprop);
					id = icalproperty_get_parameter_as_string (fbprop, "X-EWS-ID");
					summary = icalproperty_get_parameter_as_string (fbprop, "X-SUMMARY");
					location = icalproperty_get_parameter_as_string (fbprop, "X-LOCATION");

					vevent = icalcomponent_new_vevent ();

					if (id && *id) {
						icalcomponent_set_uid (vevent, id);
					} else {
						gchar *uid;

						uid = g_strdup_printf ("%s-%s-%d",
							icaltime_as_ical_string (fb.start),
							icaltime_as_ical_string (fb.end),
							(gint) fbtype);

						icalcomponent_set_uid (vevent, uid);

						g_free (uid);
					}

					fb.start.zone = utc_zone;
					fb.end.zone = utc_zone;

					icalcomponent_set_dtstart (vevent, fb.start);
					icalcomponent_set_dtend (vevent, fb.end);

					icalcomponent_add_property (vevent, icalproperty_new_created (icaltime_current_time_with_zone (utc_zone)));

					if (fbtype == ICAL_FBTYPE_FREE) {
						icalcomponent_set_summary (vevent, C_("FreeBusyType", "Free"));
						icalcomponent_add_property (vevent, icalproperty_new_transp (ICAL_TRANSP_TRANSPARENT));
					} else if (fbtype == ICAL_FBTYPE_BUSY) {
						icalcomponent_set_summary (vevent, C_("FreeBusyType", "Busy"));
					} else if (fbtype == ICAL_FBTYPE_BUSYUNAVAILABLE) {
						icalcomponent_set_summary (vevent, C_("FreeBusyType", "Out of Office"));
					} else if (fbtype == ICAL_FBTYPE_BUSYTENTATIVE) {
						icalcomponent_set_summary (vevent, C_("FreeBusyType", "Tentative"));
					}

					if (summary && *summary)
						icalcomponent_set_summary (vevent, summary);

					if (location && *location)
						icalcomponent_set_location (vevent, location);

					ecomp = g_hash_table_lookup (known, icalcomponent_get_uid (vevent));
					if (ecomp) {
						g_object_ref (ecomp);

						/* This dereferences the ecomp, thus the ref() call above to keep it alive */
						g_hash_table_remove (known, icalcomponent_get_uid (vevent));

						if (ecb_ews_freebusy_ecomp_changed (ecomp, vevent)) {
							ECalMetaBackendInfo *nfo;
							gchar *revision = e_util_generate_uid ();

							e_cal_util_set_x_property (vevent, "X-EVOLUTION-CHANGEKEY", revision);

							nfo = e_cal_meta_backend_info_new (icalcomponent_get_uid (vevent), NULL, NULL, NULL);
							nfo->revision = revision;
							nfo->object = icalcomponent_as_ical_string_r (vevent);

							*out_created_objects = g_slist_prepend (*out_created_objects, nfo);
						} else {
							icalcomponent_free (vevent);
						}

						g_clear_object (&ecomp);
					} else {
						ECalMetaBackendInfo *nfo;
						gchar *revision = e_util_generate_uid ();

						e_cal_util_set_x_property (vevent, "X-EVOLUTION-CHANGEKEY", revision);

						nfo = e_cal_meta_backend_info_new (icalcomponent_get_uid (vevent), NULL, NULL, NULL);
						nfo->revision = revision;
						nfo->object = icalcomponent_as_ical_string_r (vevent);

						*out_modified_objects = g_slist_prepend (*out_modified_objects, nfo);
					}
				}
			}

			g_hash_table_iter_init (&iter, known);
			while (g_hash_table_iter_next (&iter, &key, NULL)) {
				const gchar *uid = key;

				*out_removed_objects = g_slist_prepend (*out_removed_objects,
					e_cal_meta_backend_info_new (uid, NULL, NULL, NULL));
			}

			g_hash_table_destroy (known);
		} else if (g_error_matches (local_error, EWS_CONNECTION_ERROR, EWS_CONNECTION_ERROR_NOFREEBUSYACCESS)) {
			e_cal_meta_backend_empty_cache_sync (meta_backend, cancellable, NULL);

			e_cal_backend_notify_error (E_CAL_BACKEND (cbews), local_error->message);
			g_clear_error (&local_error);
		} else {
			g_propagate_error (error, local_error);
		}

		g_slist_free_full (free_busy, (GDestroyNotify) icalcomponent_free);
		g_slist_free_full (fbdata.user_mails, g_free);
	} else {
		GSList *items_created = NULL, *items_modified = NULL, *items_deleted = NULL, *link;
		EEwsAdditionalProps *add_props;
		gboolean includes_last_item = TRUE;

		add_props = e_ews_additional_props_new ();
		add_props->field_uri = g_strdup ("item:ItemClass");

		success = e_ews_connection_sync_folder_items_sync (cbews->priv->cnc, EWS_PRIORITY_MEDIUM,
			last_sync_tag, cbews->priv->folder_id, "IdOnly", add_props, EWS_MAX_FETCH_COUNT,
			out_new_sync_tag, &includes_last_item, &items_created, &items_modified, &items_deleted,
			cancellable, &local_error);

		if (!success &&
		    g_error_matches (local_error, EWS_CONNECTION_ERROR, EWS_CONNECTION_ERROR_INVALIDSYNCSTATEDATA)) {
			g_clear_error (&local_error);

			e_cal_meta_backend_empty_cache_sync (meta_backend, cancellable, NULL);

			success = e_ews_connection_sync_folder_items_sync (cbews->priv->cnc, EWS_PRIORITY_MEDIUM,
				NULL, cbews->priv->folder_id, "IdOnly", add_props, EWS_MAX_FETCH_COUNT,
				out_new_sync_tag, &includes_last_item, &items_created, &items_modified, &items_deleted,
				cancellable, &local_error);
		}

		e_ews_additional_props_free (add_props);

		if (success) {
			GSList *components_created = NULL, *components_modified = NULL;
			icalcomponent_kind kind;

			kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbews));

			/* The sync state doesn't cover changes made by save_component_sync(),
			   thus verify the changes, instead of re-donwloading the component again */
			items_created = ecb_ews_verify_changes (cal_cache, kind, items_created, cancellable);
			items_modified = ecb_ews_verify_changes (cal_cache, kind, items_modified, cancellable);

			if (items_created) {
				success = ecb_ews_fetch_items_sync (cbews, items_created, &components_created, cancellable, error);
				if (success)
					*out_created_objects = ecb_ews_components_to_infos (meta_backend, components_created, kind);
			}

			if (items_modified) {
				success = ecb_ews_fetch_items_sync (cbews, items_modified, &components_modified, cancellable, error);
				if (success)
					*out_modified_objects = ecb_ews_components_to_infos (meta_backend, components_modified, kind);
			}

			for (link = items_deleted; link; link = g_slist_next (link)) {
				const gchar *item_id = link->data;
				GSList *ids = NULL, *ilink;

				if (!e_cal_cache_get_ids_with_extra (cal_cache, item_id, &ids, cancellable, NULL))
					continue;

				for (ilink = ids; ilink; ilink = g_slist_next (ilink)) {
					ECalComponentId *id = ilink->data;

					/* Use the master object */
					if (id && id->uid && *id->uid && (!id->rid || !*id->rid)) {
						*out_removed_objects = g_slist_prepend (*out_removed_objects,
							e_cal_meta_backend_info_new (id->uid, NULL, NULL, NULL));
						break;
					}
				}

				g_slist_free_full (ids, (GDestroyNotify) e_cal_component_free_id);
			}

			g_slist_free_full (components_created, g_object_unref);
			g_slist_free_full (components_modified, g_object_unref);

			*out_repeat = !includes_last_item;
		} else if (local_error) {
			g_propagate_error (error, local_error);
		}

		g_slist_free_full (items_created, g_object_unref);
		g_slist_free_full (items_modified, g_object_unref);
		g_slist_free_full (items_deleted, g_free);
	}

	g_rec_mutex_unlock (&cbews->priv->cnc_lock);

	ecb_ews_convert_error_to_edc_error (error);
	ecb_ews_maybe_disconnect_sync (cbews, error, cancellable);
	g_clear_object (&cal_cache);

	return success;
}