From Mon Sep 17 00:00:00 2001
From: Matt McCutchen <matt@mattmccutchen.net>
Date: Sun, 1 Jul 2012 17:15:12 -0700

Restore support for e_cal_get_changes (EXPERIMENTAL).

For the benefit of the gnome-pilot conduits.
---
 calendar/backends/file/e-cal-backend-file.c |  143 +++++++++++++++++++++++++++
 calendar/libecal/e-cal.c                    |   66 ++++++++++++-
 calendar/libedata-cal/e-cal-backend-sync.c  |   58 +++++++++++
 calendar/libedata-cal/e-cal-backend-sync.h  |    2 +
 calendar/libedata-cal/e-cal-backend.c       |   31 ++++++
 calendar/libedata-cal/e-cal-backend.h       |    2 +
 calendar/libedata-cal/e-data-cal.c          |   79 +++++++++++++++
 calendar/libedata-cal/e-data-cal.h          |    1 +
 calendar/libegdbus/e-gdbus-cal.c            |   43 ++++++++
 calendar/libegdbus/e-gdbus-cal.h            |    9 ++
 10 files changed, 431 insertions(+), 3 deletions(-)

diff --git a/calendar/backends/file/e-cal-backend-file.c b/calendar/backends/file/e-cal-backend-file.c
index 79f4c8b..4da3f8b 100644
--- a/calendar/backends/file/e-cal-backend-file.c
+++ b/calendar/backends/file/e-cal-backend-file.c
@@ -386,6 +386,21 @@ uid_in_use (ECalBackendFile *cbfile,
 	return obj_data != NULL;
 }
 
+// lookup_component was morphed to uid_in_use after get_changes was removed.
+// Easiest is to copy the original version back. ~ Matt 2011-11-27
+/* Looks up a component by its UID on the backend's component hash table */
+static ECalComponent *
+lookup_component (ECalBackendFile *cbfile, const gchar *uid)
+{
+	ECalBackendFilePrivate *priv;
+	ECalBackendFileObject *obj_data;
+
+	priv = cbfile->priv;
+
+	obj_data = g_hash_table_lookup (priv->comp_uid_hash, uid);
+	return obj_data ? obj_data->full_object : NULL;
+}
+
 
 
 static icalproperty *
@@ -2167,6 +2182,133 @@ e_cal_backend_file_get_free_busy (ECalBackendSync *backend,
 	g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
 }
 
+typedef struct
+{
+	ECalBackendFile *backend;
+	icalcomponent_kind kind;
+	GList *deletes;
+	EXmlHash *ehash;
+} ECalBackendFileComputeChangesData;
+
+static gboolean
+e_cal_backend_file_compute_changes_foreach_key (const gchar *key, gpointer value, gpointer data)
+{
+	ECalBackendFileComputeChangesData *be_data = data;
+
+	if (!lookup_component (be_data->backend, key)) {
+		ECalComponent *comp;
+
+		comp = e_cal_component_new ();
+		// This looks wrong (what about JOURNAL?), but I don't think it
+		// matters because gnome-pilot doesn't look at the type.
+		// ~ Matt 2011-11-27
+		if (be_data->kind == ICAL_VTODO_COMPONENT)
+			e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_TODO);
+		else
+			e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
+
+		e_cal_component_set_uid (comp, key);
+		be_data->deletes = g_list_prepend (be_data->deletes, e_cal_component_get_as_string (comp));
+
+		g_object_unref (comp);
+		return TRUE;
+	}
+	return FALSE;
+}
+
+static void
+e_cal_backend_file_compute_changes (ECalBackendFile *cbfile, const gchar *change_id,
+				    GList **adds, GList **modifies, GList **deletes, GError **perror)
+{
+	ECalBackendFilePrivate *priv;
+	gchar    *filename;
+	EXmlHash *ehash;
+	ECalBackendFileComputeChangesData be_data;
+	GList *i;
+	gchar *unescaped_uri;
+
+	priv = cbfile->priv;
+
+	/* FIXME Will this always work? */
+	unescaped_uri = g_uri_unescape_string (priv->path, "");
+	filename = g_strdup_printf ("%s-%s.db", unescaped_uri, change_id);
+	g_free (unescaped_uri);
+	if (!(ehash = e_xmlhash_new (filename))) {
+		g_free (filename);
+		g_propagate_error (perror, EDC_ERROR (OtherError));
+		return;
+	}
+
+	g_free (filename);
+
+	g_static_rec_mutex_lock (&priv->idle_save_rmutex);
+
+	/* Calculate adds and modifies */
+	for (i = priv->comp; i != NULL; i = i->next) {
+		const gchar *uid;
+		gchar *calobj;
+
+		e_cal_component_get_uid (i->data, &uid);
+		calobj = e_cal_component_get_as_string (i->data);
+
+		g_assert (calobj != NULL);
+
+		/* check what type of change has occurred, if any */
+		switch (e_xmlhash_compare (ehash, uid, calobj)) {
+		case E_XMLHASH_STATUS_SAME:
+			break;
+		case E_XMLHASH_STATUS_NOT_FOUND:
+			*adds = g_list_prepend (*adds, g_strdup (calobj));
+			e_xmlhash_add (ehash, uid, calobj);
+			break;
+		case E_XMLHASH_STATUS_DIFFERENT:
+			*modifies = g_list_prepend (*modifies, g_strdup (calobj));
+			e_xmlhash_add (ehash, uid, calobj);
+			break;
+		}
+
+		g_free (calobj);
+	}
+
+	/* Calculate deletions */
+	be_data.backend = cbfile;
+	be_data.kind = e_cal_backend_get_kind (E_CAL_BACKEND (cbfile));
+	be_data.deletes = NULL;
+	be_data.ehash = ehash;
+
+	e_xmlhash_foreach_key_remove (ehash, (EXmlHashRemoveFunc) e_cal_backend_file_compute_changes_foreach_key, &be_data);
+
+	*deletes = be_data.deletes;
+
+	e_xmlhash_write (ehash);
+	e_xmlhash_destroy (ehash);
+
+	g_static_rec_mutex_unlock (&priv->idle_save_rmutex);
+}
+
+/* Get_changes handler for the file backend */
+static void
+e_cal_backend_file_get_changes (ECalBackendSync *backend,
+                                EDataCal *cal,
+                                GCancellable *cancellable,
+                                const gchar *change_id,
+                                GList **adds,
+                                GList **modifies,
+                                GList **deletes,
+                                GError **error)
+{
+	ECalBackendFile *cbfile;
+	ECalBackendFilePrivate *priv;
+
+	cbfile = E_CAL_BACKEND_FILE (backend);
+	priv = cbfile->priv;
+
+	e_return_data_cal_error_if_fail (priv->icalcomp != NULL, NoSuchCal);
+	e_return_data_cal_error_if_fail (change_id != NULL, ObjectNotFound);
+
+	e_cal_backend_file_compute_changes (cbfile, change_id, adds, modifies, deletes, error);
+}
+
 static icaltimezone *
 e_cal_backend_file_internal_get_timezone (ECalBackend *backend,
                                           const gchar *tzid)
@@ -3453,6 +3595,7 @@ e_cal_backend_file_class_init (ECalBackendFileClass *class)
 	sync_class->get_attachment_uris_sync	= e_cal_backend_file_get_attachment_uris;
 	sync_class->add_timezone_sync		= e_cal_backend_file_add_timezone;
 	sync_class->get_free_busy_sync		= e_cal_backend_file_get_free_busy;
+	sync_class->get_changes_sync		= e_cal_backend_file_get_changes;
 
 	backend_class->start_view		= e_cal_backend_file_start_view;
 	backend_class->internal_get_timezone	= e_cal_backend_file_internal_get_timezone;
diff --git a/calendar/libecal/e-cal.c b/calendar/libecal/e-cal.c
index e2e4fb9..2d3d347 100644
--- a/calendar/libecal/e-cal.c
+++ b/calendar/libecal/e-cal.c
@@ -2715,7 +2715,8 @@ e_cal_resolve_tzid_cb (const gchar *tzid,
  *
  * Returns: TRUE if the call was successful, FALSE otherwise.
  *
- * Deprecated: 3.2: This function has been dropped completely.
+ * Deprecated: 3.2: This function has been dropped completely, but Matt is
+ * restoring it for gnome-pilot.
  */
 gboolean
 e_cal_get_changes (ECal *ecal,
@@ -2724,6 +2725,7 @@ e_cal_get_changes (ECal *ecal,
                    GError **error)
 {
 	ECalPrivate *priv;
+	gchar **change_strs = NULL, *gdbus_change_id = NULL;
 
 	e_return_error_if_fail (changes != NULL, E_CALENDAR_STATUS_INVALID_ARG);
 	e_return_error_if_fail (change_id != NULL, E_CALENDAR_STATUS_INVALID_ARG);
@@ -2732,7 +2734,65 @@ e_cal_get_changes (ECal *ecal,
 	e_return_error_if_fail (priv->gdbus_cal, E_CALENDAR_STATUS_REPOSITORY_OFFLINE);
 	*changes = NULL;
 
-	E_CALENDAR_CHECK_STATUS (E_CALENDAR_STATUS_NOT_SUPPORTED, error);
+	if (priv->load_state != E_CAL_LOAD_LOADED) {
+		E_CALENDAR_CHECK_STATUS (E_CALENDAR_STATUS_URI_NOT_LOADED, error);
+	}
+
+	if (!e_gdbus_cal_call_get_changes_sync (priv->gdbus_cal, e_util_ensure_gdbus_string (change_id, &gdbus_change_id), &change_strs, NULL, error)) {
+		g_free (gdbus_change_id);
+
+		E_CALENDAR_CHECK_STATUS (E_CALENDAR_STATUS_DBUS_EXCEPTION, error);
+	}
+
+	g_free (gdbus_change_id);
+
+	if (!change_strs) {
+		E_CALENDAR_CHECK_STATUS (E_CALENDAR_STATUS_OTHER_ERROR, error);
+	} else {
+		/* TODO: Be more elegant and split this into a function */
+		/* Mostly copied from the old e-cal-listener.c */
+		gchar **p;
+
+		for (p = change_strs; *p; p++) {
+			gchar *change_str = *p;
+			ECalChangeType change_type;
+			icalcomponent *icalcomp;
+			ECalChange *change;
+
+			switch (change_str[0]) {
+			case 'A':
+				change_type = E_CAL_CHANGE_ADDED;
+				break;
+			case 'M':
+				change_type = E_CAL_CHANGE_MODIFIED;
+				break;
+			case 'D':
+				change_type = E_CAL_CHANGE_DELETED;
+				break;
+			default:
+				g_warn_if_reached();
+				continue;
+			}
+
+			icalcomp = icalparser_parse_string (change_str + 1);
+			if (!icalcomp)
+				continue;
+			change = g_new (ECalChange, 1);
+			change->comp = e_cal_component_new ();
+			if (!e_cal_component_set_icalcomponent (change->comp, icalcomp)) {
+				icalcomponent_free (icalcomp);
+				g_object_unref (G_OBJECT (change->comp));
+				g_free (change);
+				continue;
+			}
+			change->type = change_type;
+			*changes = g_list_append (*changes, change);
+		}
+
+		g_strfreev (change_strs);
+
+		E_CALENDAR_CHECK_STATUS (E_CALENDAR_STATUS_OK, error);
+	}
 }
 
 /**
@@ -2741,7 +2801,7 @@ e_cal_get_changes (ECal *ecal,
  *
  * Free a list of changes as returned by #e_cal_get_changes.
  *
- * Deprecated: 3.2: Use () instead.
+ * Deprecated: 3.2: See deprecation of e_cal_get_changes.
  */
 void
 e_cal_free_change_list (GList *list)
diff --git a/calendar/libedata-cal/e-cal-backend-sync.c b/calendar/libedata-cal/e-cal-backend-sync.c
index 34ef6a2..5dcad08 100644
--- a/calendar/libedata-cal/e-cal-backend-sync.c
+++ b/calendar/libedata-cal/e-cal-backend-sync.c
@@ -307,6 +307,36 @@ e_cal_backend_sync_get_free_busy (ECalBackendSync *backend,
 }
 
 /**
+ * e_cal_backend_sync_get_changes:
+ * @backend: An ECalBackendSync object.
+ * @cal: An EDataCal object.
+ * @cancellable: a #GCancellable for the operation
+ * @change_id: ID of the change to use as base.
+ * @adds: Placeholder for list of additions.
+ * @modifies: Placeholder for list of modifications.
+ * @deletes: Placeholder for list of deletions.
+ * @error: Out parameter for a #GError.
+ *
+ * Calls the get_changes_sync method on the given backend.
+ */
+void
+e_cal_backend_sync_get_changes (ECalBackendSync *backend,
+                                EDataCal *cal,
+                                GCancellable *cancellable,
+                                const gchar *change_id,
+                                GList **adds,
+                                GList **modifies,
+                                GList **deletes,
+                                GError **error)
+{
+	e_return_data_cal_error_if_fail (backend && E_IS_CAL_BACKEND_SYNC (backend), InvalidArg);
+	e_return_data_cal_error_if_fail (change_id, InvalidArg);
+	e_return_data_cal_error_if_fail (adds && modifies && deletes, InvalidArg);
+
+	LOCK_WRAPPER (get_changes_sync, (backend, cal, cancellable, change_id, adds, modifies, deletes, error));
+}
+
+/**
  * e_cal_backend_sync_create_object:
  * @backend: An ECalBackendSync object.
  * @cal: An EDataCal object.
@@ -729,6 +759,33 @@ cal_backend_get_free_busy (ECalBackend *backend,
 }
 
 static void
+cal_backend_get_changes (ECalBackend *backend,
+                         EDataCal *cal,
+                         guint32 opid,
+                         GCancellable *cancellable,
+                         const gchar *change_id)
+{
+	GError *error = NULL;
+	GList *adds = NULL, *modifies = NULL, *deletes = NULL, *l;
+
+	e_cal_backend_sync_get_changes (E_CAL_BACKEND_SYNC (backend), cal, cancellable, change_id, &adds, &modifies, &deletes, &error);
+
+	e_data_cal_respond_get_changes (cal, opid, error, adds, modifies, deletes);
+
+	for (l = adds; l; l = l->next)
+		g_free (l->data);
+	g_list_free (adds);
+
+	for (l = modifies; l; l = l->next)
+		g_free (l->data);
+	g_list_free (modifies);
+
+	for (l = deletes; l; l = l->next)
+		g_free (l->data);
+	g_list_free (deletes);
+}
+
+static void
 cal_backend_create_object (ECalBackend *backend,
                            EDataCal *cal,
                            guint32 opid,
@@ -1049,6 +1106,7 @@ e_cal_backend_sync_class_init (ECalBackendSyncClass *class)
 	backend_class->get_object		= cal_backend_get_object;
 	backend_class->get_object_list		= cal_backend_get_object_list;
 	backend_class->get_free_busy		= cal_backend_get_free_busy;
+	backend_class->get_changes		= cal_backend_get_changes;
 	backend_class->create_object		= cal_backend_create_object;
 	backend_class->modify_object		= cal_backend_modify_object;
 	backend_class->remove_object		= cal_backend_remove_object;
diff --git a/calendar/libedata-cal/e-cal-backend-sync.h b/calendar/libedata-cal/e-cal-backend-sync.h
index d3c2d13..4d79764 100644
--- a/calendar/libedata-cal/e-cal-backend-sync.h
+++ b/calendar/libedata-cal/e-cal-backend-sync.h
@@ -39,6 +39,7 @@ struct _ECalBackendSyncClass {
 	void	(* get_object_sync)		(ECalBackendSync *backend, EDataCal *cal, GCancellable *cancellable, const gchar *uid, const gchar *rid, gchar **calobj, GError **error);
 	void	(* get_object_list_sync)	(ECalBackendSync *backend, EDataCal *cal, GCancellable *cancellable, const gchar *sexp, GSList **calobjs, GError **error);
 	void	(* get_free_busy_sync)		(ECalBackendSync *backend, EDataCal *cal, GCancellable *cancellable, const GSList *users, time_t start, time_t end, GSList **freebusyobjs, GError **error);
+	void	(* get_changes_sync)		(ECalBackendSync *backend, EDataCal *cal, GCancellable *cancellable, const gchar *change_id, GList **adds, GList **modifies, GList **deletes, GError **error);
 	void	(* create_object_sync)		(ECalBackendSync *backend, EDataCal *cal, GCancellable *cancellable, const gchar *calobj, gchar **uid, ECalComponent **new_component, GError **error);
 	void	(* modify_object_sync)		(ECalBackendSync *backend, EDataCal *cal, GCancellable *cancellable, const gchar *calobj, CalObjModType mod, ECalComponent **old_component, ECalComponent **new_component, GError **error);
 	void	(* remove_object_sync)		(ECalBackendSync *backend, EDataCal *cal, GCancellable *cancellable, const gchar *uid, const gchar *rid, CalObjModType mod, ECalComponent **old_component, ECalComponent **new_component, GError **error);
@@ -64,6 +65,7 @@ gboolean e_cal_backend_sync_set_backend_property (ECalBackendSync *backend, EDat
 void	e_cal_backend_sync_get_object		(ECalBackendSync *backend, EDataCal *cal, GCancellable *cancellable, const gchar *uid, const gchar *rid, gchar **calobj, GError **error);
 void	e_cal_backend_sync_get_object_list	(ECalBackendSync *backend, EDataCal *cal, GCancellable *cancellable, const gchar *sexp, GSList **calobjs, GError **error);
 void	e_cal_backend_sync_get_free_busy	(ECalBackendSync *backend, EDataCal *cal, GCancellable *cancellable, const GSList *users, time_t start, time_t end, GSList **freebusyobjects, GError **error);
+void	e_cal_backend_sync_get_changes		(ECalBackendSync *backend, EDataCal *cal, GCancellable *cancellable, const gchar *change_id, GList **adds, GList **modifies, GList **deletes, GError **error);
 void	e_cal_backend_sync_create_object	(ECalBackendSync *backend, EDataCal *cal, GCancellable *cancellable, const gchar *calobj, gchar **uid, ECalComponent **new_component, GError **error);
 void	e_cal_backend_sync_modify_object	(ECalBackendSync *backend, EDataCal *cal, GCancellable *cancellable, const gchar *calobj, CalObjModType mod, ECalComponent **old_component, ECalComponent **new_component, GError **error);
 void	e_cal_backend_sync_remove_object	(ECalBackendSync *backend, EDataCal *cal, GCancellable *cancellable, const gchar *uid, const gchar *rid, CalObjModType mod, ECalComponent **old_component, ECalComponent **new_component, GError **error);
diff --git a/calendar/libedata-cal/e-cal-backend.c b/calendar/libedata-cal/e-cal-backend.c
index 1894690..28dca0b 100644
--- a/calendar/libedata-cal/e-cal-backend.c
+++ b/calendar/libedata-cal/e-cal-backend.c
@@ -977,6 +977,37 @@ e_cal_backend_get_object_list (ECalBackend *backend,
 }
 
 /**
+ * e_cal_backend_get_changes:
+ * @backend: an #ECalBackend
+ * @cal: an #EDataCal
+ * @opid: the ID to use for this operation
+ * @cancellable: a #GCancellable for the operation
+ * @change_id: ID to use for comparing changes.
+ *
+ * Calls the get_changes method on the given backend.
+ * This might be finished with e_data_cal_respond_get_changes().
+ **/
+void
+e_cal_backend_get_changes (ECalBackend *backend,
+                           EDataCal *cal,
+                           guint32 opid,
+                           GCancellable *cancellable,
+                           const gchar *change_id)
+{
+	g_return_if_fail (backend != NULL);
+	g_return_if_fail (E_IS_CAL_BACKEND (backend));
+	g_return_if_fail (change_id != NULL);
+	g_return_if_fail (E_CAL_BACKEND_GET_CLASS (backend)->get_changes != NULL);
+
+	if (e_cal_backend_is_opening (backend))
+		e_data_cal_respond_get_changes (cal, opid, EDC_OPENING_ERROR, NULL, NULL, NULL);
+	else if (!e_cal_backend_is_opened (backend))
+		e_data_cal_respond_get_changes (cal, opid, EDC_NOT_OPENED_ERROR, NULL, NULL, NULL);
+	else
+		(* E_CAL_BACKEND_GET_CLASS (backend)->get_changes) (backend, cal, opid, cancellable, change_id);
+}
+
+/**
  * e_cal_backend_get_free_busy:
  * @backend: an #ECalBackend
  * @cal: an #EDataCal
diff --git a/calendar/libedata-cal/e-cal-backend.h b/calendar/libedata-cal/e-cal-backend.h
index 13cf488..6e8cb9b 100644
--- a/calendar/libedata-cal/e-cal-backend.h
+++ b/calendar/libedata-cal/e-cal-backend.h
@@ -159,6 +159,7 @@ struct _ECalBackendClass {
 	void	(* refresh)			(ECalBackend *backend, EDataCal *cal, guint32 opid, GCancellable *cancellable);
 	void	(* get_object)			(ECalBackend *backend, EDataCal *cal, guint32 opid, GCancellable *cancellable, const gchar *uid, const gchar *rid);
 	void	(* get_object_list)		(ECalBackend *backend, EDataCal *cal, guint32 opid, GCancellable *cancellable, const gchar *sexp);
+	void	(* get_changes)			(ECalBackend *backend, EDataCal *cal, guint32 opid, GCancellable *cancellable, const gchar *change_id);
 	void	(* get_free_busy)		(ECalBackend *backend, EDataCal *cal, guint32 opid, GCancellable *cancellable, const GSList *users, time_t start, time_t end);
 	void	(* create_object)		(ECalBackend *backend, EDataCal *cal, guint32 opid, GCancellable *cancellable, const gchar *calobj);
 	void	(* modify_object)		(ECalBackend *backend, EDataCal *cal, guint32 opid, GCancellable *cancellable, const gchar *calobj, CalObjModType mod);
@@ -211,6 +212,7 @@ void		e_cal_backend_remove			(ECalBackend *backend, EDataCal *cal, guint32 opid,
 void		e_cal_backend_refresh			(ECalBackend *backend, EDataCal *cal, guint32 opid, GCancellable *cancellable);
 void		e_cal_backend_get_object		(ECalBackend *backend, EDataCal *cal, guint32 opid, GCancellable *cancellable, const gchar *uid, const gchar *rid);
 void		e_cal_backend_get_object_list		(ECalBackend *backend, EDataCal *cal, guint32 opid, GCancellable *cancellable, const gchar *sexp);
+void		e_cal_backend_get_changes		(ECalBackend *backend, EDataCal *cal, guint32 opid, GCancellable *cancellable, const gchar *change_id);
 void		e_cal_backend_get_free_busy		(ECalBackend *backend, EDataCal *cal, guint32 opid, GCancellable *cancellable, const GSList *users, time_t start, time_t end);
 void		e_cal_backend_create_object		(ECalBackend *backend, EDataCal *cal, guint32 opid, GCancellable *cancellable, const gchar *calobj);
 void		e_cal_backend_modify_object		(ECalBackend *backend, EDataCal *cal, guint32 opid, GCancellable *cancellable, const gchar *calobj, CalObjModType mod);
diff --git a/calendar/libedata-cal/e-data-cal.c b/calendar/libedata-cal/e-data-cal.c
index cd20dec..7823d2e 100644
--- a/calendar/libedata-cal/e-data-cal.c
+++ b/calendar/libedata-cal/e-data-cal.c
@@ -71,6 +71,7 @@ typedef enum {
 	OP_GET_OBJECT,
 	OP_GET_OBJECT_LIST,
 	OP_GET_FREE_BUSY,
+	OP_GET_CHANGES,
 	OP_CREATE_OBJECT,
 	OP_MODIFY_OBJECT,
 	OP_REMOVE_OBJECT,
@@ -117,6 +118,8 @@ typedef struct {
 			time_t start, end;
 			GSList *users;
 		} fb;
+		/* OP_GET_CHANGES */
+		gchar *change_id;
 		/* OP_CREATE_OBJECT */
 		/* OP_RECEIVE_OBJECTS */
 		/* OP_SEND_OBJECTS */
@@ -217,6 +220,10 @@ operation_thread (gpointer data,
 		g_slist_foreach (op->d.fb.users, (GFunc) g_free, NULL);
 		g_slist_free (op->d.fb.users);
 		break;
+	case OP_GET_CHANGES:
+		e_cal_backend_get_changes (backend, op->cal, op->id, op->cancellable, op->d.change_id);
+		g_free (op->d.change_id);
+		break;
 	case OP_CREATE_OBJECT:
 		e_cal_backend_create_object (backend, op->cal, op->id, op->cancellable, op->d.co.calobj);
 		g_free (op->d.co.calobj);
@@ -679,6 +686,23 @@ impl_Cal_get_free_busy (EGdbusCal *object,
 }
 
 static gboolean
+impl_Cal_get_changes (EGdbusCal *object,
+                      GDBusMethodInvocation *invocation,
+                      const gchar *in_change_id,
+                      EDataCal *cal)
+{
+	OperationData *op;
+
+	op = op_new (OP_GET_CHANGES, cal);
+	op->d.change_id = g_strdup (in_change_id);
+
+	e_gdbus_cal_complete_get_changes (cal->priv->gdbus_object, invocation, op->id);
+	e_operation_pool_push (ops_pool, op);
+
+	return TRUE;
+}
+
+static gboolean
 impl_Cal_create_object (EGdbusCal *object,
                         GDBusMethodInvocation *invocation,
                         const gchar *in_calobj,
@@ -1163,6 +1187,60 @@ e_data_cal_respond_get_free_busy (EDataCal *cal,
 }
 
 /**
+ * e_data_cal_respond_get_changes:
+ * @cal: A calendar client interface.
+ * @error: Operation error, if any, automatically freed if passed it.
+ * @adds: List of additions.
+ * @modifies: List of modifications.
+ * @deletes: List of removals.
+ *
+ * Notifies listeners of the completion of the get_changes method call.
+ */
+void
+e_data_cal_respond_get_changes (EDataCal *cal,
+                                guint32 opid,
+                                GError *error,
+                                const GList *adds,
+                                const GList *modifies,
+                                const GList *deletes)
+{
+	GSList *change_strs = NULL;
+	const GList *l;
+	gchar **strv_change_strs;
+
+	op_complete (cal, opid);
+
+	/* Translators: This is prefix to a detailed error message */
+	g_prefix_error (&error, "%s", _("Cannot retrieve calendar changes: "));
+
+	// This is some code duplication, but pulling out a subroutine hurts
+	// readability too much... ~ Matt 2011-11-27
+
+	for (l = adds; l; l = l->next)
+		change_strs = g_slist_prepend(change_strs,
+				g_strdup_printf("A%s", (const gchar *) l->data));
+
+	for (l = modifies; l; l = l->next)
+		change_strs = g_slist_prepend(change_strs,
+				g_strdup_printf("M%s", (const gchar *) l->data));
+
+	for (l = deletes; l; l = l->next)
+		change_strs = g_slist_prepend(change_strs,
+				g_strdup_printf("D%s", (const gchar *) l->data));
+
+	strv_change_strs = gslist_to_strv (change_strs);
+
+	g_slist_foreach (change_strs, (GFunc) g_free, NULL);
+	g_slist_free (change_strs);
+
+	e_gdbus_cal_emit_get_changes_done (cal->priv->gdbus_object, opid, error, (const gchar * const *) strv_change_strs);
+
+	g_strfreev (strv_change_strs);
+	if (error)
+		g_error_free (error);
+}
+
+/**
  * e_data_cal_respond_create_object:
  * @cal: A calendar client interface.
  * @error: Operation error, if any, automatically freed if passed it.
@@ -1639,6 +1717,7 @@ data_cal_set_property (GObject *object,
 	}
 
 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+	g_signal_connect (gdbus_object, "handle-get-changes", G_CALLBACK (impl_Cal_get_changes), ecal);
 }
 
 static void
diff --git a/calendar/libedata-cal/e-data-cal.h b/calendar/libedata-cal/e-data-cal.h
index f9c5ecd..92b5e89 100644
--- a/calendar/libedata-cal/e-data-cal.h
+++ b/calendar/libedata-cal/e-data-cal.h
@@ -136,6 +136,7 @@ void		e_data_cal_respond_set_backend_property		(EDataCal *cal, guint32 opid, GEr
 void		e_data_cal_respond_get_object			(EDataCal *cal, guint32 opid, GError *error, const gchar *object);
 void		e_data_cal_respond_get_object_list		(EDataCal *cal, guint32 opid, GError *error, const GSList *objects);
 void		e_data_cal_respond_get_free_busy		(EDataCal *cal, guint32 opid, GError *error);
+void		e_data_cal_respond_get_changes			(EDataCal *cal, guint32 opid, GError *error, const GList *adds, const GList *modifies, const GList *deletes);
 void		e_data_cal_respond_create_object		(EDataCal *cal, guint32 opid, GError *error, const gchar *uid, /*const */ ECalComponent *new_component);
 void		e_data_cal_respond_modify_object		(EDataCal *cal, guint32 opid, GError *error, /* const */ ECalComponent *old_component, /* const */ ECalComponent *new_component);
 void		e_data_cal_respond_remove_object		(EDataCal *cal, guint32 opid, GError *error, const ECalComponentId *id, /* const */ ECalComponent *old_component, /* const */ ECalComponent *new_component);
diff --git a/calendar/libegdbus/e-gdbus-cal.c b/calendar/libegdbus/e-gdbus-cal.c
index 8c9212a..a84b4be 100644
--- a/calendar/libegdbus/e-gdbus-cal.c
+++ b/calendar/libegdbus/e-gdbus-cal.c
@@ -63,6 +63,8 @@ enum
 	__GET_OBJECT_LIST_DONE_SIGNAL,
 	__GET_FREE_BUSY_METHOD,
 	__GET_FREE_BUSY_DONE_SIGNAL,
+	__GET_CHANGES_METHOD,
+	__GET_CHANGES_DONE_SIGNAL,
 	__CREATE_OBJECT_METHOD,
 	__CREATE_OBJECT_DONE_SIGNAL,
 	__MODIFY_OBJECT_METHOD,
@@ -163,6 +165,8 @@ E_DECLARE_GDBUS_METHOD_DONE_EMISSION_HOOK_ASYNC_STRV (GDBUS_CAL_INTERFACE_NAME,
                                                       get_object_list)
 E_DECLARE_GDBUS_METHOD_DONE_EMISSION_HOOK_ASYNC_VOID (GDBUS_CAL_INTERFACE_NAME,
                                                       get_free_busy)
+E_DECLARE_GDBUS_METHOD_DONE_EMISSION_HOOK_ASYNC_STRV (GDBUS_CAL_INTERFACE_NAME,
+                                                      get_changes)
 E_DECLARE_GDBUS_METHOD_DONE_EMISSION_HOOK_ASYNC_STRING (GDBUS_CAL_INTERFACE_NAME,
                                                         create_object)
 E_DECLARE_GDBUS_METHOD_DONE_EMISSION_HOOK_ASYNC_VOID (GDBUS_CAL_INTERFACE_NAME,
@@ -211,6 +215,7 @@ e_gdbus_cal_default_init (EGdbusCalIface *iface)
 	E_INIT_GDBUS_METHOD_ASYNC_STRV__STRING	(EGdbusCalIface, "get_object",			get_object, __GET_OBJECT_METHOD, __GET_OBJECT_DONE_SIGNAL)
 	E_INIT_GDBUS_METHOD_ASYNC_STRING__STRV	(EGdbusCalIface, "get_object_list",		get_object_list, __GET_OBJECT_LIST_METHOD, __GET_OBJECT_LIST_DONE_SIGNAL)
 	E_INIT_GDBUS_METHOD_ASYNC_STRV__VOID	(EGdbusCalIface, "get_free_busy",		get_free_busy, __GET_FREE_BUSY_METHOD, __GET_FREE_BUSY_DONE_SIGNAL)
+	E_INIT_GDBUS_METHOD_ASYNC_STRING__STRV	(EGdbusCalIface, "get_changes",			get_changes, __GET_CHANGES_METHOD, __GET_CHANGES_DONE_SIGNAL)
 	E_INIT_GDBUS_METHOD_ASYNC_STRING__STRING(EGdbusCalIface, "create_object",		create_object, __CREATE_OBJECT_METHOD, __CREATE_OBJECT_DONE_SIGNAL)
 	E_INIT_GDBUS_METHOD_ASYNC_STRV__VOID	(EGdbusCalIface, "modify_object",		modify_object, __MODIFY_OBJECT_METHOD, __MODIFY_OBJECT_DONE_SIGNAL)
 	E_INIT_GDBUS_METHOD_ASYNC_STRV__VOID	(EGdbusCalIface, "remove_object",		remove_object, __REMOVE_OBJECT_METHOD, __REMOVE_OBJECT_DONE_SIGNAL)
@@ -547,6 +552,37 @@ e_gdbus_cal_call_get_free_busy_sync (GDBusProxy *proxy,
 }
 
 void
+e_gdbus_cal_call_get_changes (GDBusProxy *proxy,
+                              const gchar *in_change_id,
+                              GCancellable *cancellable,
+                              GAsyncReadyCallback callback,
+                              gpointer user_data)
+{
+	e_gdbus_proxy_call_string ("get_changes", e_gdbus_cal_call_get_changes, E_GDBUS_ASYNC_OP_KEEPER (proxy), in_change_id, cancellable, callback, user_data);
+}
+
+gboolean
+e_gdbus_cal_call_get_changes_finish (GDBusProxy *proxy,
+                                     GAsyncResult *result,
+                                     gchar ***out_change_strs,
+                                     GError **error)
+{
+	return e_gdbus_proxy_finish_call_strv (E_GDBUS_ASYNC_OP_KEEPER (proxy), result, out_change_strs, error, e_gdbus_cal_call_get_changes);
+}
+
+gboolean
+e_gdbus_cal_call_get_changes_sync (GDBusProxy *proxy,
+                                   const gchar *in_change_id,
+                                   gchar ***out_change_strs,
+                                   GCancellable *cancellable,
+                                   GError **error)
+{
+	return e_gdbus_proxy_call_sync_string__strv (proxy, in_change_id, out_change_strs, cancellable, error,
+		e_gdbus_cal_call_get_changes,
+		e_gdbus_cal_call_get_changes_finish);
+}
+
+void
 e_gdbus_cal_call_create_object (GDBusProxy *proxy,
                                 const gchar *in_calobj,
                                 GCancellable *cancellable,
@@ -1163,6 +1199,9 @@ DECLARE_EMIT_DONE_SIGNAL_1 (get_object_list,
                             const gchar * const *)
 DECLARE_EMIT_DONE_SIGNAL_0 (get_free_busy,
                             __GET_FREE_BUSY_DONE_SIGNAL)
+DECLARE_EMIT_DONE_SIGNAL_1 (get_changes,
+                            __GET_CHANGES_DONE_SIGNAL,
+                            const gchar * const *)
 DECLARE_EMIT_DONE_SIGNAL_1 (create_object,
                             __CREATE_OBJECT_DONE_SIGNAL,
                             const gchar *)
@@ -1259,6 +1298,7 @@ E_DECLARE_GDBUS_ASYNC_METHOD_1			(cal, set_backend_property, propnamevalue, "as"
 E_DECLARE_GDBUS_ASYNC_METHOD_1_WITH_RETURN	(cal, get_object, uid_rid, "as", object, "s")
 E_DECLARE_GDBUS_ASYNC_METHOD_1_WITH_RETURN	(cal, get_object_list, sexp, "s", objects, "as")
 E_DECLARE_GDBUS_ASYNC_METHOD_1			(cal, get_free_busy, start_stop_users, "as")
+E_DECLARE_GDBUS_ASYNC_METHOD_1_WITH_RETURN	(cal, get_changes, change_id, "s", change_strs, "as")
 E_DECLARE_GDBUS_ASYNC_METHOD_1_WITH_RETURN	(cal, create_object, object, "s", uid, "s")
 E_DECLARE_GDBUS_ASYNC_METHOD_1			(cal, modify_object, object_mod, "as")
 E_DECLARE_GDBUS_ASYNC_METHOD_1			(cal, remove_object, uid_rid_mod, "as")
@@ -1287,6 +1327,7 @@ static const GDBusMethodInfo * const e_gdbus_cal_method_info_pointers[] =
 	&E_DECLARED_GDBUS_METHOD_INFO_NAME (cal, get_object),
 	&E_DECLARED_GDBUS_METHOD_INFO_NAME (cal, get_object_list),
 	&E_DECLARED_GDBUS_METHOD_INFO_NAME (cal, get_free_busy),
+	&E_DECLARED_GDBUS_METHOD_INFO_NAME (cal, get_changes),
 	&E_DECLARED_GDBUS_METHOD_INFO_NAME (cal, create_object),
 	&E_DECLARED_GDBUS_METHOD_INFO_NAME (cal, modify_object),
 	&E_DECLARED_GDBUS_METHOD_INFO_NAME (cal, remove_object),
@@ -1322,6 +1363,7 @@ static const GDBusSignalInfo * const e_gdbus_cal_signal_info_pointers[] =
 	&E_DECLARED_GDBUS_SIGNAL_INFO_NAME (cal, get_object_done),
 	&E_DECLARED_GDBUS_SIGNAL_INFO_NAME (cal, get_object_list_done),
 	&E_DECLARED_GDBUS_SIGNAL_INFO_NAME (cal, get_free_busy_done),
+	&E_DECLARED_GDBUS_SIGNAL_INFO_NAME (cal, get_changes_done),
 	&E_DECLARED_GDBUS_SIGNAL_INFO_NAME (cal, create_object_done),
 	&E_DECLARED_GDBUS_SIGNAL_INFO_NAME (cal, modify_object_done),
 	&E_DECLARED_GDBUS_SIGNAL_INFO_NAME (cal, remove_object_done),
@@ -1561,6 +1603,7 @@ e_gdbus_cal_proxy_init (EGdbusCalProxy *proxy)
 	E_GDBUS_CONNECT_METHOD_DONE_SIGNAL_STRING (get_object);
 	E_GDBUS_CONNECT_METHOD_DONE_SIGNAL_STRV   (get_object_list);
 	E_GDBUS_CONNECT_METHOD_DONE_SIGNAL_VOID   (get_free_busy);
+	E_GDBUS_CONNECT_METHOD_DONE_SIGNAL_STRV   (get_changes);
 	E_GDBUS_CONNECT_METHOD_DONE_SIGNAL_STRING (create_object);
 	E_GDBUS_CONNECT_METHOD_DONE_SIGNAL_VOID   (modify_object);
 	E_GDBUS_CONNECT_METHOD_DONE_SIGNAL_VOID   (remove_object);
diff --git a/calendar/libegdbus/e-gdbus-cal.h b/calendar/libegdbus/e-gdbus-cal.h
index 695d13b..f7a11f6 100644
--- a/calendar/libegdbus/e-gdbus-cal.h
+++ b/calendar/libegdbus/e-gdbus-cal.h
@@ -136,6 +136,9 @@ struct _EGdbusCalIface
 	gboolean (*handle_get_free_busy)		(EGdbusCal *object, GDBusMethodInvocation *invocation, const gchar * const *in_start_end_userlist);
 	void	 (*get_free_busy_done)			(EGdbusCal *object, guint arg_opid, const GError *arg_error);
 
+	gboolean (*handle_get_changes)			(EGdbusCal *object, GDBusMethodInvocation *invocation, const gchar *in_change_id);
+	void	 (*get_changes_done)			(EGdbusCal *object, guint arg_opid, const GError *arg_error, gchar ***out_change_strs);
+
 	gboolean (*handle_create_object)		(EGdbusCal *object, GDBusMethodInvocation *invocation, const gchar *in_calobj);
 	void	 (*create_object_done)			(EGdbusCal *object, guint arg_opid, const GError *arg_error, gchar **out_uid);
 
@@ -211,6 +214,10 @@ void		e_gdbus_cal_call_get_free_busy			(GDBusProxy *proxy, const gchar * const *
 gboolean	e_gdbus_cal_call_get_free_busy_finish		(GDBusProxy *proxy, GAsyncResult *result, GError **error);
 gboolean	e_gdbus_cal_call_get_free_busy_sync		(GDBusProxy *proxy, const gchar * const *in_start_end_userlist, GCancellable *cancellable, GError **error);
 
+void		e_gdbus_cal_call_get_changes			(GDBusProxy *proxy, const gchar *in_change_id, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data);
+gboolean	e_gdbus_cal_call_get_changes_finish		(GDBusProxy *proxy, GAsyncResult *result, gchar ***out_change_strs, GError **error);
+gboolean	e_gdbus_cal_call_get_changes_sync		(GDBusProxy *proxy, const gchar *in_change_id, gchar ***out_change_strs, GCancellable *cancellable, GError **error);
+
 void		e_gdbus_cal_call_create_object			(GDBusProxy *proxy, const gchar *in_calobj, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data);
 gboolean	e_gdbus_cal_call_create_object_finish		(GDBusProxy *proxy, GAsyncResult *result, gchar **out_uid, GError **error);
 gboolean	e_gdbus_cal_call_create_object_sync		(GDBusProxy *proxy, const gchar *in_calobj, gchar **out_uid, GCancellable *cancellable, GError **error);
@@ -286,6 +293,7 @@ gboolean	e_gdbus_cal_call_close_sync			(GDBusProxy *proxy, GCancellable *cancell
 #define e_gdbus_cal_complete_get_object			e_gdbus_complete_async_method
 #define e_gdbus_cal_complete_get_object_list		e_gdbus_complete_async_method
 #define e_gdbus_cal_complete_get_free_busy		e_gdbus_complete_async_method
+#define e_gdbus_cal_complete_get_changes		e_gdbus_complete_async_method
 #define e_gdbus_cal_complete_create_object		e_gdbus_complete_async_method
 #define e_gdbus_cal_complete_modify_object		e_gdbus_complete_async_method
 #define e_gdbus_cal_complete_remove_object		e_gdbus_complete_async_method
@@ -310,6 +318,7 @@ void e_gdbus_cal_emit_get_object_done			(EGdbusCal *object, guint arg_opid, cons
 void e_gdbus_cal_emit_get_object_list_done		(EGdbusCal *object, guint arg_opid, const GError *arg_error, const gchar * const *out_objects);
 void e_gdbus_cal_emit_get_free_busy_done		(EGdbusCal *object, guint arg_opid, const GError *arg_error);
 void e_gdbus_cal_emit_get_free_busy_data		(EGdbusCal *object, guint arg_opid, const GError *arg_error, const gchar * const *out_freebusy);
+void e_gdbus_cal_emit_get_changes_done			(EGdbusCal *object, guint arg_opid, const GError *arg_error, const gchar * const *out_change_strs);
 void e_gdbus_cal_emit_create_object_done		(EGdbusCal *object, guint arg_opid, const GError *arg_error, const gchar *out_uid);
 void e_gdbus_cal_emit_modify_object_done		(EGdbusCal *object, guint arg_opid, const GError *arg_error);
 void e_gdbus_cal_emit_remove_object_done		(EGdbusCal *object, guint arg_opid, const GError *arg_error);
