[evolution-data-server/evolution-data-server-3-12] Bug 652132 - Google Tasks support



commit 344fa7cfad5ec93fd7249c89efd7ad4508df1070
Author: Milan Crha <mcrha redhat com>
Date:   Fri Jun 20 10:57:33 2014 +0200

    Bug 652132 - Google Tasks support

 calendar/backends/Makefile.am                      |    8 +-
 calendar/backends/gtasks/Makefile.am               |   42 +
 .../backends/gtasks/e-cal-backend-gtasks-factory.c |   67 +
 calendar/backends/gtasks/e-cal-backend-gtasks.c    | 1442 ++++++++++++++++++++
 calendar/backends/gtasks/e-cal-backend-gtasks.h    |   62 +
 .../backends/gtasks/e-gdata-oauth2-authorizer.c    |  326 +++++
 .../backends/gtasks/e-gdata-oauth2-authorizer.h    |   69 +
 configure.ac                                       |    8 +
 modules/google-backend/module-google-backend.c     |   84 ++-
 9 files changed, 2104 insertions(+), 4 deletions(-)
---
diff --git a/calendar/backends/Makefile.am b/calendar/backends/Makefile.am
index b282566..672c417 100644
--- a/calendar/backends/Makefile.am
+++ b/calendar/backends/Makefile.am
@@ -1,9 +1,15 @@
+if  HAVE_GDATA_0_15_1
+GTASKS_SUBDIR = gtasks
+else
+GTASKS_SUBDIR =
+endif
+
 if ENABLE_WEATHER
 WEATHER_SUBDIR = weather
 else
 WEATHER_SUBDIR =
 endif
 
-SUBDIRS = file http contacts $(WEATHER_SUBDIR) caldav
+SUBDIRS = file http contacts $(WEATHER_SUBDIR) caldav $(GTASKS_SUBDIR)
 
 -include $(top_srcdir)/git.mk
diff --git a/calendar/backends/gtasks/Makefile.am b/calendar/backends/gtasks/Makefile.am
new file mode 100644
index 0000000..cb2e2e0
--- /dev/null
+++ b/calendar/backends/gtasks/Makefile.am
@@ -0,0 +1,42 @@
+NULL =
+
+ecal_backend_LTLIBRARIES = libecalbackendgtasks.la
+
+libecalbackendgtasks_la_CPPFLAGS = \
+       $(AM_CPPFLAGS) \
+       -I$(top_srcdir) \
+       -I$(top_srcdir)/calendar \
+       -I$(top_builddir)/calendar \
+       $(EVOLUTION_CALENDAR_CFLAGS) \
+       $(CAMEL_CFLAGS) \
+       $(GDATA_CFLAGS) \
+       $(SOUP_CFLAGS) \
+       -DG_LOG_DOMAIN=\"e-cal-backend-gtasks\" \
+       $(CODE_COVERAGE_CFLAGS) \
+       $(NULL)
+
+libecalbackendgtasks_la_SOURCES = \
+       e-cal-backend-gtasks-factory.c \
+       e-cal-backend-gtasks.c \
+       e-cal-backend-gtasks.h \
+       e-gdata-oauth2-authorizer.c \
+       e-gdata-oauth2-authorizer.h \
+       $(NULL)
+
+libecalbackendgtasks_la_LIBADD = \
+       $(top_builddir)/calendar/libedata-cal/libedata-cal-1.2.la \
+       $(top_builddir)/calendar/libecal/libecal-1.2.la \
+       $(top_builddir)/libedataserver/libedataserver-1.2.la \
+       $(top_builddir)/libebackend/libebackend-1.2.la \
+       $(EVOLUTION_CALENDAR_LIBS) \
+       $(CAMEL_LIBS) \
+       $(GDATA_LIBS) \
+       $(SOUP_LIBS) \
+       $(NULL)
+
+libecalbackendgtasks_la_LDFLAGS = \
+       -module -avoid-version $(NO_UNDEFINED) \
+       $(CODE_COVERAGE_LDFLAGS) \
+       $(NULL)
+
+-include $(top_srcdir)/git.mk
diff --git a/calendar/backends/gtasks/e-cal-backend-gtasks-factory.c 
b/calendar/backends/gtasks/e-cal-backend-gtasks-factory.c
new file mode 100644
index 0000000..c0eeab9
--- /dev/null
+++ b/calendar/backends/gtasks/e-cal-backend-gtasks-factory.c
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2014 Red Hat, Inc. (www.redhat.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Milan Crha <mcrha redhat com>
+ */
+
+#include <config.h>
+
+#include "e-cal-backend-gtasks.h"
+
+#define FACTORY_NAME "gtasks"
+
+typedef ECalBackendFactory ECalBackendGTasksFactory;
+typedef ECalBackendFactoryClass ECalBackendGTasksFactoryClass;
+
+/* Module Entry Points */
+void e_module_load (GTypeModule *type_module);
+void e_module_unload (GTypeModule *type_module);
+
+/* Forward Declarations */
+GType e_cal_backend_gtasks_factory_get_type (void);
+
+G_DEFINE_DYNAMIC_TYPE (
+       ECalBackendGTasksFactory,
+       e_cal_backend_gtasks_factory,
+       E_TYPE_CAL_BACKEND_FACTORY)
+
+static void
+e_cal_backend_gtasks_factory_class_init (ECalBackendFactoryClass *class)
+{
+       class->factory_name = FACTORY_NAME;
+       class->component_kind = ICAL_VTODO_COMPONENT;
+       class->backend_type = E_TYPE_CAL_BACKEND_GTASKS;
+}
+
+static void
+e_cal_backend_gtasks_factory_class_finalize (ECalBackendFactoryClass *class)
+{
+}
+
+static void
+e_cal_backend_gtasks_factory_init (ECalBackendFactory *factory)
+{
+}
+
+G_MODULE_EXPORT void
+e_module_load (GTypeModule *type_module)
+{
+       e_cal_backend_gtasks_factory_register_type (type_module);
+}
+
+G_MODULE_EXPORT void
+e_module_unload (GTypeModule *type_module)
+{
+}
diff --git a/calendar/backends/gtasks/e-cal-backend-gtasks.c b/calendar/backends/gtasks/e-cal-backend-gtasks.c
new file mode 100644
index 0000000..9f68cb7
--- /dev/null
+++ b/calendar/backends/gtasks/e-cal-backend-gtasks.c
@@ -0,0 +1,1442 @@
+/*
+ * Copyright (C) 2014 Red Hat, Inc. (www.redhat.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Milan Crha <mcrha redhat com>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <glib/gi18n-lib.h>
+#include <gdata/gdata.h>
+
+#include "e-gdata-oauth2-authorizer.h"
+#include "e-cal-backend-gtasks.h"
+
+#define d(x)
+
+#define E_CAL_BACKEND_GTASKS_GET_PRIVATE(obj) \
+       (G_TYPE_INSTANCE_GET_PRIVATE \
+       ((obj), E_TYPE_CAL_BACKEND_GTASKS, ECalBackendGTasksPrivate))
+
+#define EDC_ERROR(_code) e_data_cal_create_error (_code, NULL)
+#define EDC_ERROR_EX(_code, _msg) e_data_cal_create_error (_code, _msg)
+
+#define GTASKS_KEY_LAST_UPDATED "last-updated"
+#define X_EVO_GTASKS_SELF_LINK "X-EVOLUTION-GTASKS-SELF-LINK"
+
+#define PROPERTY_LOCK(_gtasks) g_mutex_lock (&(_gtasks)->priv->property_mutex)
+#define PROPERTY_UNLOCK(_gtasks) g_mutex_unlock (&(_gtasks)->priv->property_mutex)
+
+struct _ECalBackendGTasksPrivate {
+       GDataAuthorizer *authorizer;
+       GDataTasksService *service;
+       GDataTasksTasklist *tasklist;
+
+       ECalBackendStore *store;
+       GCancellable *cancellable;
+       GMutex property_mutex;
+
+       guint refresh_id;
+};
+
+G_DEFINE_TYPE (ECalBackendGTasks, e_cal_backend_gtasks, E_TYPE_CAL_BACKEND)
+
+static GCancellable *
+ecb_gtasks_ref_cancellable (ECalBackendGTasks *gtasks)
+{
+       GCancellable *cancellable = NULL;
+
+       g_return_val_if_fail (E_IS_CAL_BACKEND_GTASKS (gtasks), NULL);
+
+       PROPERTY_LOCK (gtasks);
+
+       if (gtasks->priv->cancellable)
+               cancellable = g_object_ref (gtasks->priv->cancellable);
+
+       PROPERTY_UNLOCK (gtasks);
+
+       return cancellable;
+}
+
+static void
+ecb_gtasks_take_cancellable (ECalBackendGTasks *gtasks,
+                            GCancellable *cancellable)
+{
+       GCancellable *old_cancellable;
+
+       g_return_if_fail (E_IS_CAL_BACKEND_GTASKS (gtasks));
+
+       PROPERTY_LOCK (gtasks);
+
+       old_cancellable = gtasks->priv->cancellable;
+       gtasks->priv->cancellable = cancellable;
+
+       PROPERTY_UNLOCK (gtasks);
+
+       if (old_cancellable) {
+               g_cancellable_cancel (old_cancellable);
+               g_clear_object (&old_cancellable);
+       }
+}
+
+static void
+ecb_gtasks_icomp_x_prop_set (icalcomponent *comp,
+                            const gchar *key,
+                            const gchar *value)
+{
+       icalproperty *xprop;
+
+       /* Find the old one first */
+       xprop = icalcomponent_get_first_property (comp, ICAL_X_PROPERTY);
+
+       while (xprop) {
+               const gchar *str = icalproperty_get_x_name (xprop);
+
+               if (!strcmp (str, key)) {
+                       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);
+       }
+
+       if (!xprop && value) {
+               xprop = icalproperty_new_x (value);
+               icalproperty_set_x_name (xprop, key);
+               icalcomponent_add_property (comp, xprop);
+       }
+}
+
+static gchar *
+ecb_gtasks_icomp_x_prop_get (icalcomponent *comp,
+                            const gchar *key)
+{
+       icalproperty *xprop;
+
+       /* Find the old one first */
+       xprop = icalcomponent_get_first_property (comp, ICAL_X_PROPERTY);
+
+       while (xprop) {
+               const gchar *str = icalproperty_get_x_name (xprop);
+
+               if (!strcmp (str, key)) {
+                       break;
+               }
+
+               xprop = icalcomponent_get_next_property (comp, ICAL_X_PROPERTY);
+       }
+
+       if (xprop) {
+               return icalproperty_get_value_as_string_r (xprop);
+       }
+
+       return NULL;
+}
+
+/* May hold PROPERTY_LOCK() when calling this */
+static ECalComponent *
+ecb_gtasks_get_cached_comp (ECalBackendGTasks *gtasks,
+                           const gchar *uid)
+{
+       g_return_val_if_fail (E_IS_CAL_BACKEND_GTASKS (gtasks), NULL);
+       g_return_val_if_fail (uid != NULL, NULL);
+
+       return e_cal_backend_store_get_component (gtasks->priv->store, uid, NULL);
+}
+
+static gboolean
+ecb_gtasks_is_authorized (ECalBackend *backend)
+{
+       ECalBackendGTasks *gtasks = E_CAL_BACKEND_GTASKS (backend);
+
+       if (!gtasks->priv->service)
+               return FALSE;
+
+       return gdata_service_is_authorized (GDATA_SERVICE (gtasks->priv->service));
+}
+
+static void
+ecb_gtasks_prepare_tasklist (ECalBackendGTasks *gtasks,
+                            GCancellable *cancellable,
+                            GError **error)
+{
+       ESourceResource *resource;
+       ESource *source;
+       gchar *id;
+
+       g_return_if_fail (E_IS_CAL_BACKEND_GTASKS (gtasks));
+       g_return_if_fail (ecb_gtasks_is_authorized (E_CAL_BACKEND (gtasks)));
+
+       source = e_backend_get_source (E_BACKEND (gtasks));
+       resource = e_source_get_extension (source, E_SOURCE_EXTENSION_RESOURCE);
+       id = e_source_resource_dup_identity (resource);
+
+       /* If the tasklist ID is not set, then pick the first from the list, most likely the "Default List" */
+       if (!id || !*id) {
+               GDataFeed *feed;
+               GDataQuery *query;
+
+               query = gdata_query_new_with_limits (NULL, 0, 1);
+               feed = gdata_tasks_service_query_all_tasklists (gtasks->priv->service, query, cancellable, 
NULL, NULL, error);
+               if (feed) {
+                       GList *entries;
+
+                       entries = gdata_feed_get_entries (feed);
+                       if (entries) {
+                               GDataEntry *entry = entries->data;
+                               if (entry) {
+                                       g_free (id);
+                                       id = g_strdup (gdata_entry_get_id (entry));
+                               }
+                       }
+                       g_clear_object (&feed);
+               }
+               g_object_unref (query);
+       }
+
+       if (!id || !*id) {
+               /* But the tests for change will not work */
+               g_free (id);
+               id = g_strdup ("@default");
+       }
+
+       g_clear_object (&gtasks->priv->tasklist);
+       gtasks->priv->tasklist = gdata_tasks_tasklist_new (id);
+
+       g_free (id);
+}
+
+static void
+ecb_gtasks_update_ical_time_property (icalcomponent *icomp,
+                                     icalproperty_kind kind,
+                                     icalproperty * (* prop_new_func) (struct icaltimetype v),
+                                     void (* prop_set_func) (icalproperty *prop, struct icaltimetype v),
+                                     struct icaltimetype t)
+{
+       icalproperty *prop;
+
+       prop = icalcomponent_get_first_property (icomp, kind);
+       if (prop) {
+               prop_set_func (prop, t);
+       } else {
+               prop = prop_new_func (t);
+               icalcomponent_add_property (icomp, prop);
+       }
+}
+
+static ECalComponent *
+ecb_gtasks_gdata_to_comp (GDataTasksTask *task)
+{
+       GDataEntry *entry;
+       GDataLink *data_link;
+       ECalComponent *comp;
+       icalcomponent *icomp;
+       const gchar *text;
+       struct icaltimetype tt;
+
+       g_return_val_if_fail (GDATA_IS_TASKS_TASK (task), NULL);
+
+       entry = GDATA_ENTRY (task);
+       icomp = icalcomponent_new (ICAL_VTODO_COMPONENT);
+
+       icalcomponent_set_uid (icomp, gdata_entry_get_id (entry));
+
+       tt = icaltime_from_timet_with_zone (gdata_entry_get_published (entry), 0, 
icaltimezone_get_utc_timezone ());
+       if (!icaltime_is_valid_time (tt) || icaltime_is_null_time (tt))
+               tt = icaltime_from_timet_with_zone (gdata_entry_get_updated (entry), 0, 
icaltimezone_get_utc_timezone ());
+       if (!icaltime_is_valid_time (tt) || icaltime_is_null_time (tt))
+               tt = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
+
+       ecb_gtasks_update_ical_time_property (icomp, ICAL_CREATED_PROPERTY,
+               icalproperty_new_created,
+               icalproperty_set_created,
+               tt);
+
+       tt = icaltime_from_timet_with_zone (gdata_entry_get_updated (entry), 0, icaltimezone_get_utc_timezone 
());
+       if (!icaltime_is_valid_time (tt) || icaltime_is_null_time (tt))
+               tt = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
+       icalcomponent_set_dtstamp (icomp, tt);
+
+       ecb_gtasks_update_ical_time_property (icomp, ICAL_LASTMODIFIED_PROPERTY,
+               icalproperty_new_lastmodified,
+               icalproperty_set_lastmodified,
+               tt);
+
+       if (gdata_tasks_task_get_due (task) > 0) {
+               tt = icaltime_from_timet (gdata_tasks_task_get_due (task), 1);
+               if (icaltime_is_valid_time (tt) && !icaltime_is_null_time (tt))
+                       icalcomponent_set_due (icomp, tt);
+       }
+
+       if (gdata_tasks_task_get_completed (task) > 0) {
+               tt = icaltime_from_timet (gdata_tasks_task_get_completed (task), 1);
+               if (icaltime_is_valid_time (tt) && !icaltime_is_null_time (tt))
+                       ecb_gtasks_update_ical_time_property (icomp, ICAL_COMPLETED_PROPERTY,
+                               icalproperty_new_completed,
+                               icalproperty_set_completed,
+                               tt);
+       }
+
+       text = gdata_entry_get_title (entry);
+       if (text && *text)
+               icalcomponent_set_summary (icomp, text);
+
+       text = gdata_tasks_task_get_notes (task);
+       if (text && *text)
+               icalcomponent_set_description (icomp, text);
+
+       /* "needsAction" or "completed" */
+       text = gdata_tasks_task_get_status (task);
+       if (g_strcmp0 (text, "completed") == 0)
+               icalcomponent_set_status (icomp, ICAL_STATUS_COMPLETED);
+       else if (g_strcmp0 (text, "needsAction") == 0)
+               icalcomponent_set_status (icomp, ICAL_STATUS_NEEDSACTION);
+
+       data_link = gdata_entry_look_up_link (entry, GDATA_LINK_SELF);
+       if (data_link)
+               ecb_gtasks_icomp_x_prop_set (icomp, X_EVO_GTASKS_SELF_LINK, gdata_link_get_uri (data_link));
+
+       comp = e_cal_component_new_from_icalcomponent (icomp);
+       g_warn_if_fail (comp != NULL);
+
+       return comp;
+}
+
+static GDataTasksTask *
+ecb_gtasks_comp_to_gdata (ECalComponent *comp)
+{
+       GDataEntry *entry;
+       GDataTasksTask *task;
+       icalcomponent *icomp;
+       icalproperty *prop;
+       const gchar *text;
+       gchar *tmp;
+       struct icaltimetype tt;
+
+       g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), NULL);
+
+       icomp = e_cal_component_get_icalcomponent (comp);
+       g_return_val_if_fail (icomp != NULL, NULL);
+
+       text = icalcomponent_get_uid (icomp);
+       task = gdata_tasks_task_new (text && *text ? text : NULL);
+       entry = GDATA_ENTRY (task);
+
+       tt = icalcomponent_get_due (icomp);
+       if (icaltime_is_valid_time (tt) && !icaltime_is_null_time (tt)) {
+               gint64 due;
+
+               due = (gint64) icaltime_as_timet_with_zone (tt, icaltimezone_get_utc_timezone ());
+               gdata_tasks_task_set_due (task, due);
+       }
+
+       prop = icalcomponent_get_first_property (icomp, ICAL_COMPLETED_PROPERTY);
+       if (prop) {
+               tt = icalproperty_get_completed (prop);
+
+               if (icaltime_is_valid_time (tt) && !icaltime_is_null_time (tt)) {
+                       gint64 completed;
+
+                       completed = (gint64) icaltime_as_timet_with_zone (tt, icaltimezone_get_utc_timezone 
());
+                       gdata_tasks_task_set_completed (task, completed);
+                       gdata_tasks_task_set_status (task, "completed");
+               }
+       }
+
+       text = icalcomponent_get_summary (icomp);
+       if (text && *text)
+               gdata_entry_set_title (entry, text);
+
+       text = icalcomponent_get_description (icomp);
+       if (text && *text)
+               gdata_tasks_task_set_notes (task, text);
+
+       /* "needsAction" or "completed" */
+       if (icalcomponent_get_status (icomp) == ICAL_STATUS_COMPLETED)
+               gdata_tasks_task_set_status (task, "completed");
+       else if (icalcomponent_get_status (icomp) == ICAL_STATUS_NEEDSACTION)
+               gdata_tasks_task_set_status (task, "needsAction");
+
+       tmp = ecb_gtasks_icomp_x_prop_get (icomp, X_EVO_GTASKS_SELF_LINK);
+       if (tmp && *tmp) {
+               GDataLink *data_link;
+
+               data_link = gdata_link_new (tmp, GDATA_LINK_SELF);
+               gdata_entry_add_link (entry, data_link);
+               g_object_unref (data_link);
+       }
+
+       g_free (tmp);
+
+       return task;
+}
+
+struct EGTasksUpdateData
+{
+       ECalBackendGTasks *gtasks;
+       gint64 taskslist_time;
+};
+
+static gpointer
+ecb_gtasks_update_thread (gpointer user_data)
+{
+       struct EGTasksUpdateData *update_data = user_data;
+       ECalBackendGTasks *gtasks;
+       GTimeVal last_updated;
+       GDataFeed *feed;
+       GDataTasksQuery *tasks_query;
+       const gchar *key;
+       GCancellable *cancellable;
+       GError *local_error = NULL;
+
+       g_return_val_if_fail (update_data != NULL, NULL);
+
+       gtasks = update_data->gtasks;
+
+       g_return_val_if_fail (E_IS_CAL_BACKEND_GTASKS (gtasks), NULL);
+
+       if (!ecb_gtasks_is_authorized (E_CAL_BACKEND (gtasks))) {
+               g_clear_object (&gtasks);
+               g_free (update_data);
+               return NULL;
+       }
+
+       PROPERTY_LOCK (gtasks);
+
+       key = e_cal_backend_store_get_key_value (gtasks->priv->store, GTASKS_KEY_LAST_UPDATED);
+       if (!key || !g_time_val_from_iso8601 (key, &last_updated))
+               last_updated.tv_sec = 0;
+
+       PROPERTY_UNLOCK (gtasks);
+
+       cancellable = ecb_gtasks_ref_cancellable (gtasks);
+
+       tasks_query = gdata_tasks_query_new (NULL);
+       gdata_query_set_start_index (GDATA_QUERY (tasks_query), 0);
+       gdata_query_set_max_results (GDATA_QUERY (tasks_query), G_MAXINT);
+       gdata_tasks_query_set_show_completed (tasks_query, TRUE);
+       gdata_tasks_query_set_show_hidden (tasks_query, TRUE);
+
+       if (last_updated.tv_sec > 0) {
+               gdata_query_set_updated_min (GDATA_QUERY (tasks_query), last_updated.tv_sec);
+               gdata_tasks_query_set_show_deleted (tasks_query, TRUE);
+       }
+
+       feed = gdata_tasks_service_query_tasks (gtasks->priv->service, gtasks->priv->tasklist,
+               GDATA_QUERY (tasks_query), cancellable, NULL, NULL, &local_error);
+       if (feed) {
+               GList *link;
+               const gchar *uid;
+
+               PROPERTY_LOCK (gtasks);
+
+               e_cal_backend_store_freeze_changes (gtasks->priv->store);
+
+               for (link = gdata_feed_get_entries (feed); link; link = g_list_next (link)) {
+                       GDataTasksTask *task = link->data;
+                       ECalComponent *cached_comp;
+
+                       if (!GDATA_IS_TASKS_TASK (task))
+                               continue;
+
+                       uid = gdata_entry_get_id (GDATA_ENTRY (task));
+                       if (!uid || !*uid)
+                               continue;
+
+                       cached_comp = ecb_gtasks_get_cached_comp (gtasks, uid);
+
+                       if (gdata_tasks_task_is_deleted (task)) {
+                               ECalComponentId id;
+
+                               id.uid = (gchar *) uid;
+                               id.rid = NULL;
+
+                               e_cal_backend_notify_component_removed ((ECalBackend *) gtasks, &id, 
cached_comp, NULL);
+                               if (cached_comp)
+                                       e_cal_backend_store_remove_component (gtasks->priv->store, uid, NULL);
+                       } else {
+                               ECalComponent *new_comp;
+
+                               new_comp = ecb_gtasks_gdata_to_comp (task);
+                               if (new_comp) {
+                                       if (cached_comp) {
+                                               struct icaltimetype *cached_tt = NULL, *new_tt = NULL;
+
+                                               e_cal_component_get_last_modified (cached_comp, &cached_tt);
+                                               e_cal_component_get_last_modified (new_comp, &new_tt);
+
+                                               if (!cached_tt || !new_tt ||
+                                                   icaltime_compare (*cached_tt, *new_tt) != 0) {
+                                                       /* Google doesn't store/provide 'created', thus use 
'created,
+                                                          as first seen by the backend' */
+                                                       if (cached_tt)
+                                                               e_cal_component_set_created (new_comp, 
cached_tt);
+
+                                                       e_cal_backend_store_put_component 
(gtasks->priv->store, new_comp);
+                                                       e_cal_backend_notify_component_modified ((ECalBackend 
*) gtasks, cached_comp, new_comp);
+                                               }
+
+                                               if (cached_tt)
+                                                       e_cal_component_free_icaltimetype (cached_tt);
+                                               if (new_tt)
+                                                       e_cal_component_free_icaltimetype (new_tt);
+                                       } else {
+                                               e_cal_backend_store_put_component (gtasks->priv->store, 
new_comp);
+                                               e_cal_backend_notify_component_created ((ECalBackend *) 
gtasks, new_comp);
+                                       }
+                               }
+
+                               g_clear_object (&new_comp);
+                       }
+
+                       g_clear_object (&cached_comp);
+               }
+
+               e_cal_backend_store_thaw_changes (gtasks->priv->store);
+
+               PROPERTY_UNLOCK (gtasks);
+       }
+
+       g_clear_object (&tasks_query);
+       g_clear_object (&feed);
+
+       if (!g_cancellable_is_cancelled (cancellable) && !local_error) {
+               gchar *strtm;
+
+               PROPERTY_LOCK (gtasks);
+
+               last_updated.tv_sec = update_data->taskslist_time;
+               last_updated.tv_usec = 0;
+
+               strtm = g_time_val_to_iso8601 (&last_updated);
+               e_cal_backend_store_put_key_value (gtasks->priv->store, GTASKS_KEY_LAST_UPDATED, strtm);
+               g_free (strtm);
+
+               PROPERTY_UNLOCK (gtasks);
+       }
+
+       g_clear_object (&cancellable);
+       g_clear_object (&gtasks);
+       g_clear_error (&local_error);
+       g_free (update_data);
+
+       return NULL;
+}
+
+static void
+ecb_gtasks_start_update (ECalBackendGTasks *gtasks)
+{
+       GDataFeed *feed;
+       GCancellable *cancellable;
+       GError *local_error = NULL;
+       gchar *id = NULL;
+       gint64 taskslist_time = 0;
+       gboolean changed = TRUE;
+
+       g_return_if_fail (E_IS_CAL_BACKEND_GTASKS (gtasks));
+
+       if (!ecb_gtasks_is_authorized ((ECalBackend *) gtasks))
+               return;
+
+       cancellable = ecb_gtasks_ref_cancellable (gtasks);
+       g_return_if_fail (cancellable != NULL);
+
+       g_object_get (gtasks->priv->tasklist, "id", &id, NULL);
+       g_return_if_fail (id != NULL);
+
+       /* Check whether the tasklist changed */
+       feed = gdata_tasks_service_query_all_tasklists (gtasks->priv->service, NULL, cancellable, NULL, NULL, 
&local_error);
+       if (feed) {
+               GList *link;
+
+               for (link = gdata_feed_get_entries (feed); link; link = g_list_next (link)) {
+                       GDataEntry *entry = link->data;
+
+                       if (entry && g_strcmp0 (id, gdata_entry_get_id (entry)) == 0) {
+                               taskslist_time = gdata_entry_get_updated (entry);
+
+                               if (taskslist_time > 0) {
+                                       GTimeVal stored;
+                                       const gchar *key;
+
+                                       PROPERTY_LOCK (gtasks);
+
+                                       key = e_cal_backend_store_get_key_value (gtasks->priv->store, 
GTASKS_KEY_LAST_UPDATED);
+                                       if (key && g_time_val_from_iso8601 (key, &stored))
+                                               changed = taskslist_time != stored.tv_sec;
+
+                                       PROPERTY_UNLOCK (gtasks);
+                               }
+
+                               break;
+                       }
+               }
+
+               g_clear_object (&feed);
+       }
+
+       if (changed && !g_cancellable_is_cancelled (cancellable)) {
+               GThread *thread;
+               struct EGTasksUpdateData *data;
+
+               data = g_new0 (struct EGTasksUpdateData, 1);
+               data->gtasks = g_object_ref (gtasks);
+               data->taskslist_time = taskslist_time;
+
+               thread = g_thread_new (NULL, ecb_gtasks_update_thread, data);
+               g_thread_unref (thread);
+       }
+
+       if (local_error) {
+               g_debug ("%s: Failed to get all tasklists: %s", G_STRFUNC, local_error->message);
+               g_clear_error (&local_error);
+       }
+
+       g_clear_object (&cancellable);
+       g_free (id);
+}
+
+static void
+ecb_gtasks_time_to_refresh_data_cb (ESource *source,
+                                   gpointer user_data)
+{
+       ECalBackendGTasks *gtasks = user_data;
+
+       g_return_if_fail (E_IS_CAL_BACKEND_GTASKS (gtasks));
+
+       if (!ecb_gtasks_is_authorized (E_CAL_BACKEND (gtasks)) ||
+           !e_backend_get_online (E_BACKEND (gtasks))) {
+               return;
+       }
+
+       ecb_gtasks_start_update (gtasks);
+}
+
+static gboolean
+ecb_gtasks_request_authorization (ECalBackend *backend,
+                                 GCancellable *cancellable,
+                                 GError **error)
+{
+       ECalBackendGTasks *gtasks = E_CAL_BACKEND_GTASKS (backend);
+
+       /* Make sure we have the GDataService configured
+        * before requesting authorization. */
+
+       if (!gtasks->priv->authorizer) {
+               ESource *source;
+               ESourceAuthentication *extension;
+               EGDataOAuth2Authorizer *authorizer;
+               const gchar *extension_name;
+               gchar *method;
+
+               extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
+               source = e_backend_get_source (E_BACKEND (backend));
+               extension = e_source_get_extension (source, extension_name);
+               method = e_source_authentication_dup_method (extension);
+
+               /* Only OAuth2 is supported with Google Tasks */
+               authorizer = e_gdata_oauth2_authorizer_new (source);
+               gtasks->priv->authorizer = GDATA_AUTHORIZER (authorizer);
+
+               g_free (method);
+       }
+
+       if (!gtasks->priv->service) {
+               GDataTasksService *tasks_service;
+
+               tasks_service = gdata_tasks_service_new (gtasks->priv->authorizer);
+               gtasks->priv->service = tasks_service;
+
+               g_object_bind_property (
+                       backend, "proxy-resolver",
+                       gtasks->priv->service, "proxy-resolver",
+                       G_BINDING_SYNC_CREATE);
+       }
+
+       /* If we're using OAuth tokens, then as far as the backend
+        * is concerned it's always authorized.  The GDataAuthorizer
+        * will take care of everything in the background. */
+       if (!GDATA_IS_CLIENT_LOGIN_AUTHORIZER (gtasks->priv->authorizer))
+               return TRUE;
+
+       /* Otherwise it's up to us to obtain a login secret. */
+       return e_backend_authenticate_sync (
+               E_BACKEND (backend),
+               E_SOURCE_AUTHENTICATOR (backend),
+               cancellable, error);
+}
+
+static gchar *
+ecb_gtasks_get_backend_property (ECalBackend *backend,
+                                const gchar *prop_name)
+{
+       g_return_val_if_fail (E_IS_CAL_BACKEND_GTASKS (backend), NULL);
+       g_return_val_if_fail (prop_name != NULL, NULL);
+
+       if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_CAPABILITIES)) {
+               GString *caps;
+
+               caps = g_string_new (
+                       CAL_STATIC_CAPABILITY_NO_THISANDFUTURE ","
+                       CAL_STATIC_CAPABILITY_NO_THISANDPRIOR ","
+                       CAL_STATIC_CAPABILITY_REFRESH_SUPPORTED);
+
+               return g_string_free (caps, FALSE);
+
+       } else if (g_str_equal (prop_name, CAL_BACKEND_PROPERTY_CAL_EMAIL_ADDRESS) ||
+                  g_str_equal (prop_name, CAL_BACKEND_PROPERTY_ALARM_EMAIL_ADDRESS)) {
+               ESourceAuthentication *authentication;
+               ESource *source;
+               const gchar *user;
+
+               source = e_backend_get_source (E_BACKEND (backend));
+               authentication = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION);
+               user = e_source_authentication_get_user (authentication);
+
+               if (!user || !*user || !strchr (user, '@'))
+                       return NULL;
+
+               return g_strdup (user);
+
+       } else if (g_str_equal (prop_name, CAL_BACKEND_PROPERTY_DEFAULT_OBJECT)) {
+               ECalComponent *comp;
+               gchar *prop_value;
+
+               comp = e_cal_component_new ();
+               e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_TODO);
+
+               prop_value = e_cal_component_get_as_string (comp);
+
+               g_object_unref (comp);
+
+               return prop_value;
+       }
+
+       /* Chain up to parent's method. */
+       return E_CAL_BACKEND_CLASS (e_cal_backend_gtasks_parent_class)->get_backend_property (backend, 
prop_name);
+}
+
+static void
+ecb_gtasks_open (ECalBackend *backend,
+                EDataCal *cal,
+                guint32 opid,
+                GCancellable *cancellable,
+                gboolean only_if_exists)
+{
+       ECalBackendGTasks *gtasks;
+       GError *local_error = NULL;
+
+       g_return_if_fail (E_IS_CAL_BACKEND_GTASKS (backend));
+       g_return_if_fail (E_IS_DATA_CAL (cal));
+
+       if (ecb_gtasks_is_authorized (backend)) {
+               e_data_cal_respond_open (cal, opid, NULL);
+               return;
+       }
+
+       gtasks = E_CAL_BACKEND_GTASKS (backend);
+
+       e_cal_backend_set_writable (backend, FALSE);
+
+       ecb_gtasks_take_cancellable (gtasks, g_cancellable_new ());
+
+       if (e_backend_get_online (E_BACKEND (backend))) {
+               gboolean success;
+
+               success = ecb_gtasks_request_authorization (backend, cancellable, &local_error);
+               if (success)
+                       success = gdata_authorizer_refresh_authorization (gtasks->priv->authorizer, 
cancellable, &local_error);
+
+               if (success) {
+                       e_cal_backend_set_writable (backend, TRUE);
+
+                       ecb_gtasks_prepare_tasklist (gtasks, cancellable, &local_error);
+                       if (!local_error)
+                               ecb_gtasks_start_update (gtasks);
+               }
+       }
+
+       e_data_cal_respond_open (cal, opid, local_error);
+}
+
+static void
+ecb_gtasks_refresh (ECalBackend *backend,
+                   EDataCal *cal,
+                   guint32 opid,
+                   GCancellable *cancellable)
+{
+       g_return_if_fail (E_IS_CAL_BACKEND_GTASKS (backend));
+       g_return_if_fail (E_IS_DATA_CAL (cal));
+
+       if (!ecb_gtasks_is_authorized (backend) ||
+           !e_backend_get_online (E_BACKEND (backend))) {
+               e_data_cal_respond_refresh (cal, opid, EDC_ERROR (RepositoryOffline));
+               return;
+       }
+
+       ecb_gtasks_start_update (E_CAL_BACKEND_GTASKS (backend));
+
+       e_data_cal_respond_refresh (cal, opid, NULL);
+}
+
+static void
+ecb_gtasks_get_object (ECalBackend *backend,
+                      EDataCal *cal,
+                      guint32 opid,
+                      GCancellable *cancellable,
+                      const gchar *uid,
+                      const gchar *rid)
+{
+       ECalBackendGTasks *gtasks;
+       ECalComponent *cached_comp;
+       gchar *comp_str = NULL;
+       GError *local_error = NULL;
+
+       g_return_if_fail (E_IS_CAL_BACKEND_GTASKS (backend));
+       g_return_if_fail (E_IS_DATA_CAL (cal));
+
+       gtasks = E_CAL_BACKEND_GTASKS (backend);
+
+       PROPERTY_LOCK (gtasks);
+
+       cached_comp = ecb_gtasks_get_cached_comp (gtasks, uid);
+       if (cached_comp)
+               comp_str = e_cal_component_get_as_string (cached_comp);
+       else
+               local_error = EDC_ERROR (ObjectNotFound);
+
+       PROPERTY_UNLOCK (gtasks);
+
+       e_data_cal_respond_get_object (cal, opid, local_error, comp_str);
+
+       g_clear_object (&cached_comp);
+       g_free (comp_str);
+}
+
+static void
+ecb_gtasks_get_object_list (ECalBackend *backend,
+                           EDataCal *cal,
+                           guint32 opid,
+                           GCancellable *cancellable,
+                           const gchar *sexp_str)
+{
+       ECalBackendGTasks *gtasks;
+       ECalBackendSExp *sexp;
+       ETimezoneCache *cache;
+       gboolean do_search;
+       GSList *list, *iter, *calobjs = NULL;
+       time_t occur_start = -1, occur_end = -1;
+       gboolean prunning_by_time;
+
+       g_return_if_fail (E_IS_CAL_BACKEND_GTASKS (backend));
+       g_return_if_fail (E_IS_DATA_CAL (cal));
+
+       gtasks = E_CAL_BACKEND_GTASKS (backend);
+
+       sexp = e_cal_backend_sexp_new (sexp_str);
+       if (sexp == NULL) {
+               e_data_cal_respond_get_object_list (cal, opid, EDC_ERROR (InvalidQuery), NULL);
+               return;
+       }
+
+       do_search = !g_str_equal (sexp_str, "#t");
+       prunning_by_time = e_cal_backend_sexp_evaluate_occur_times (sexp, &occur_start, &occur_end);
+
+       cache = E_TIMEZONE_CACHE (backend);
+
+       PROPERTY_LOCK (gtasks);
+
+       list = prunning_by_time ?
+               e_cal_backend_store_get_components_occuring_in_range (gtasks->priv->store, occur_start, 
occur_end)
+               : e_cal_backend_store_get_components (gtasks->priv->store);
+
+       PROPERTY_UNLOCK (gtasks);
+
+       for (iter = list; iter; iter = g_slist_next (iter)) {
+               ECalComponent *comp = E_CAL_COMPONENT (iter->data);
+
+               if (!do_search || e_cal_backend_sexp_match_comp (sexp, comp, cache)) {
+                       calobjs = g_slist_prepend (calobjs, e_cal_component_get_as_string (comp));
+               }
+
+               g_object_unref (comp);
+       }
+
+       g_object_unref (sexp);
+       g_slist_free (list);
+
+       e_data_cal_respond_get_object_list (cal, opid, NULL, calobjs);
+
+       g_slist_foreach (calobjs, (GFunc) g_free, NULL);
+       g_slist_free (calobjs);
+}
+
+static void
+ecb_gtasks_get_free_busy (ECalBackend *backend,
+                         EDataCal *cal,
+                         guint32 opid,
+                         GCancellable *cancellable,
+                         const GSList *users,
+                         time_t start,
+                         time_t end)
+{
+       g_return_if_fail (E_IS_CAL_BACKEND_GTASKS (backend));
+       g_return_if_fail (E_IS_DATA_CAL (cal));
+
+       e_data_cal_respond_get_free_busy (cal, opid, EDC_ERROR (NotSupported));
+}
+
+static void
+ecb_gtasks_create_objects (ECalBackend *backend,
+                          EDataCal *cal,
+                          guint32 opid,
+                          GCancellable *cancellable,
+                          const GSList *calobjs)
+{
+       ECalBackendGTasks *gtasks;
+       GSList *new_uids = NULL, *new_calcomps = NULL;
+       const GSList *link;
+       GError *local_error = NULL;
+
+       g_return_if_fail (E_IS_CAL_BACKEND_GTASKS (backend));
+       g_return_if_fail (E_IS_DATA_CAL (cal));
+
+       gtasks = E_CAL_BACKEND_GTASKS (backend);
+
+       if (!ecb_gtasks_is_authorized (backend) ||
+           !e_backend_get_online (E_BACKEND (backend))) {
+               e_data_cal_respond_create_objects (cal, opid, EDC_ERROR (RepositoryOffline), NULL, NULL);
+               return;
+       }
+
+       for (link = calobjs; link && !local_error; link = link->next) {
+               const gchar *icalstr = link->data;
+               ECalComponent *comp;
+               icalcomponent *icomp;
+               const gchar *uid;
+               GDataTasksTask *new_task, *comp_task;
+
+               if (!icalstr) {
+                       local_error = EDC_ERROR (InvalidObject);
+                       break;
+               }
+
+               comp = e_cal_component_new_from_string (icalstr);
+               if (comp == NULL) {
+                       local_error = EDC_ERROR (InvalidObject);
+                       break;
+               }
+
+               icomp = e_cal_component_get_icalcomponent (comp);
+               if (!icomp) {
+                       g_object_unref (comp);
+                       local_error = EDC_ERROR (InvalidObject);
+                       break;
+               }
+
+               uid = icalcomponent_get_uid (icomp);
+               if (uid) {
+                       PROPERTY_LOCK (gtasks);
+
+                       if (e_cal_backend_store_has_component (gtasks->priv->store, uid, NULL)) {
+                               PROPERTY_UNLOCK (gtasks);
+                               g_object_unref (comp);
+                               local_error = EDC_ERROR (ObjectIdAlreadyExists);
+                               break;
+                       }
+
+                       PROPERTY_UNLOCK (gtasks);
+
+                       icalcomponent_set_uid (icomp, "");
+               }
+
+               comp_task = ecb_gtasks_comp_to_gdata (comp);
+               if (!comp_task) {
+                       g_object_unref (comp);
+                       local_error = EDC_ERROR (InvalidObject);
+                       break;
+               }
+
+               new_task = gdata_tasks_service_insert_task (gtasks->priv->service, comp_task, 
gtasks->priv->tasklist, cancellable, &local_error);
+
+               g_object_unref (comp_task);
+               g_object_unref (comp);
+
+               if (!new_task)
+                       break;
+
+               comp = ecb_gtasks_gdata_to_comp (new_task);
+               g_object_unref (new_task);
+
+               if (!comp) {
+                       local_error = EDC_ERROR (InvalidObject);
+                       break;
+               }
+
+               icomp = e_cal_component_get_icalcomponent (comp);
+               uid = icalcomponent_get_uid (icomp);
+
+               if (!uid) {
+                       g_object_unref (comp);
+                       local_error = EDC_ERROR (InvalidObject);
+                       break;
+               }
+
+               PROPERTY_LOCK (gtasks);
+               e_cal_backend_store_put_component (gtasks->priv->store, comp);
+               PROPERTY_UNLOCK (gtasks);
+
+               e_cal_backend_notify_component_created (backend, comp);
+
+               new_uids = g_slist_prepend (new_uids, g_strdup (uid));
+               new_calcomps = g_slist_prepend (new_calcomps, comp);
+       }
+
+       new_uids = g_slist_reverse (new_uids);
+       new_calcomps = g_slist_reverse (new_calcomps);
+
+       e_data_cal_respond_create_objects (cal, opid, local_error, new_uids, new_calcomps);
+
+       g_slist_free_full (new_uids, g_free);
+       e_util_free_nullable_object_slist (new_calcomps);
+}
+
+static void
+ecb_gtasks_modify_objects (ECalBackend *backend,
+                          EDataCal *cal,
+                          guint32 opid,
+                          GCancellable *cancellable,
+                          const GSList *calobjs,
+                          ECalObjModType mod)
+{
+       ECalBackendGTasks *gtasks;
+       GSList *old_calcomps = NULL, *new_calcomps = NULL;
+       const GSList *link;
+       GError *local_error = NULL;
+
+       g_return_if_fail (E_IS_CAL_BACKEND_GTASKS (backend));
+       g_return_if_fail (E_IS_DATA_CAL (cal));
+
+       gtasks = E_CAL_BACKEND_GTASKS (backend);
+
+       if (!ecb_gtasks_is_authorized (backend) ||
+           !e_backend_get_online (E_BACKEND (backend))) {
+               e_data_cal_respond_modify_objects (cal, opid, EDC_ERROR (RepositoryOffline), NULL, NULL);
+               return;
+       }
+
+       for (link = calobjs; link && !local_error; link = link->next) {
+               const gchar *icalstr = link->data;
+               ECalComponent *comp, *cached_comp;
+               icalcomponent *icomp;
+               const gchar *uid;
+               GDataTasksTask *new_task, *comp_task;
+
+               if (!icalstr) {
+                       local_error = EDC_ERROR (InvalidObject);
+                       break;
+               }
+
+               comp = e_cal_component_new_from_string (icalstr);
+               if (comp == NULL) {
+                       local_error = EDC_ERROR (InvalidObject);
+                       break;
+               }
+
+               icomp = e_cal_component_get_icalcomponent (comp);
+               if (!icomp) {
+                       g_object_unref (comp);
+                       local_error = EDC_ERROR (InvalidObject);
+                       break;
+               }
+
+               uid = icalcomponent_get_uid (icomp);
+               if (!uid) {
+                       g_object_unref (comp);
+                       local_error = EDC_ERROR (InvalidObject);
+                       break;
+               }
+
+               PROPERTY_LOCK (gtasks);
+
+               cached_comp = ecb_gtasks_get_cached_comp (gtasks, uid);
+
+               PROPERTY_UNLOCK (gtasks);
+
+               if (!cached_comp) {
+                       g_object_unref (comp);
+                       local_error = EDC_ERROR (ObjectNotFound);
+                       break;
+               }
+
+               comp_task = ecb_gtasks_comp_to_gdata (comp);
+               g_object_unref (comp);
+
+               if (!comp_task) {
+                       g_object_unref (cached_comp);
+                       local_error = EDC_ERROR (ObjectNotFound);
+                       break;
+               }
+
+               new_task = gdata_tasks_service_update_task (gtasks->priv->service, comp_task, cancellable, 
&local_error);
+               g_object_unref (comp_task);
+
+               if (!new_task) {
+                       g_object_unref (cached_comp);
+                       break;
+               }
+
+               comp = ecb_gtasks_gdata_to_comp (new_task);
+               g_object_unref (new_task);
+
+               PROPERTY_LOCK (gtasks);
+               e_cal_backend_store_put_component (gtasks->priv->store, comp);
+               PROPERTY_UNLOCK (gtasks);
+
+               e_cal_backend_notify_component_modified (backend, cached_comp, comp);
+
+               old_calcomps = g_slist_prepend (old_calcomps, cached_comp);
+               new_calcomps = g_slist_prepend (new_calcomps, comp);
+       }
+
+       old_calcomps = g_slist_reverse (old_calcomps);
+       new_calcomps = g_slist_reverse (new_calcomps);
+
+       e_data_cal_respond_modify_objects (cal, opid, local_error, old_calcomps, new_calcomps);
+
+       e_util_free_nullable_object_slist (old_calcomps);
+       e_util_free_nullable_object_slist (new_calcomps);
+}
+
+static void
+ecb_gtasks_remove_objects (ECalBackend *backend,
+                          EDataCal *cal,
+                          guint32 opid,
+                          GCancellable *cancellable,
+                          const GSList *ids,
+                          ECalObjModType mod)
+{
+       ECalBackendGTasks *gtasks;
+       GSList *old_calcomps = NULL, *removed_ids = NULL;
+       const GSList *link;
+       GError *local_error = NULL;
+
+       g_return_if_fail (E_IS_CAL_BACKEND_GTASKS (backend));
+       g_return_if_fail (E_IS_DATA_CAL (cal));
+
+       gtasks = E_CAL_BACKEND_GTASKS (backend);
+
+       if (!ecb_gtasks_is_authorized (backend) ||
+           !e_backend_get_online (E_BACKEND (backend))) {
+               e_data_cal_respond_remove_objects (cal, opid, EDC_ERROR (RepositoryOffline), NULL, NULL, 
NULL);
+               return;
+       }
+
+       for (link = ids; link; link = link->next) {
+               const ECalComponentId *id = link->data;
+               ECalComponentId *tmp_id;
+               ECalComponent *cached_comp;
+               GDataTasksTask *task;
+
+               if (!id || !id->uid) {
+                       local_error = EDC_ERROR (InvalidObject);
+                       break;
+               }
+
+               PROPERTY_LOCK (gtasks);
+               cached_comp = ecb_gtasks_get_cached_comp (gtasks, id->uid);
+               PROPERTY_UNLOCK (gtasks);
+
+               if (!cached_comp) {
+                       local_error = EDC_ERROR (ObjectNotFound);
+                       break;
+               }
+
+               task = ecb_gtasks_comp_to_gdata (cached_comp);
+               if (!task) {
+                       g_object_unref (cached_comp);
+                       local_error = EDC_ERROR (InvalidObject);
+                       break;
+               }
+
+               /* Ignore protocol errors here, libgdata 0.15.1 results with "Error code 204 when deleting an 
entry: No Content",
+                  while the delete succeeded */
+               if (!gdata_tasks_service_delete_task (gtasks->priv->service, task, cancellable, &local_error) 
&&
+                   !g_error_matches (local_error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR)) {
+                       g_object_unref (cached_comp);
+                       g_object_unref (task);
+                       break;
+               }
+
+               g_clear_error (&local_error);
+
+               g_object_unref (task);
+
+               PROPERTY_LOCK (gtasks);
+               e_cal_backend_store_remove_component (gtasks->priv->store, id->uid, NULL);
+               PROPERTY_UNLOCK (gtasks);
+
+               tmp_id = e_cal_component_id_new (id->uid, NULL);
+               e_cal_backend_notify_component_removed (backend, tmp_id, cached_comp, NULL);
+
+               old_calcomps = g_slist_prepend (old_calcomps, cached_comp);
+               removed_ids = g_slist_prepend (removed_ids, tmp_id);
+       }
+
+       old_calcomps = g_slist_reverse (old_calcomps);
+       removed_ids = g_slist_reverse (removed_ids);
+
+       e_data_cal_respond_remove_objects (cal, opid, local_error, removed_ids, old_calcomps, NULL);
+
+       g_slist_free_full (removed_ids, (GDestroyNotify) e_cal_component_free_id);
+       e_util_free_nullable_object_slist (old_calcomps);
+}
+
+static void
+ecb_gtasks_receive_objects (ECalBackend *backend,
+                           EDataCal *cal,
+                           guint32 opid,
+                           GCancellable *cancellable,
+                           const gchar *calobj)
+{
+       g_return_if_fail (E_IS_CAL_BACKEND_GTASKS (backend));
+       g_return_if_fail (E_IS_DATA_CAL (cal));
+
+       e_data_cal_respond_receive_objects (cal, opid, EDC_ERROR (NotSupported));
+}
+
+static void
+ecb_gtasks_send_objects (ECalBackend *backend,
+                        EDataCal *cal,
+                        guint32 opid,
+                        GCancellable *cancellable,
+                        const gchar *calobj)
+{
+       g_return_if_fail (E_IS_CAL_BACKEND_GTASKS (backend));
+       g_return_if_fail (E_IS_DATA_CAL (cal));
+
+       e_data_cal_respond_send_objects (cal, opid, EDC_ERROR (NotSupported), NULL, NULL);
+}
+
+static void
+ecb_gtasks_get_attachment_uris (ECalBackend *backend,
+                               EDataCal *cal,
+                               guint32 opid,
+                               GCancellable *cancellable,
+                               const gchar *uid,
+                               const gchar *rid)
+{
+       g_return_if_fail (E_IS_CAL_BACKEND_GTASKS (backend));
+       g_return_if_fail (E_IS_DATA_CAL (cal));
+
+       e_data_cal_respond_get_attachment_uris (cal, opid, EDC_ERROR (NotSupported), NULL);
+}
+
+static void
+ecb_gtasks_discard_alarm (ECalBackend *backend,
+                         EDataCal *cal,
+                         guint32 opid,
+                         GCancellable *cancellable,
+                         const gchar *uid,
+                         const gchar *rid,
+                         const gchar *auid)
+{
+       g_return_if_fail (E_IS_CAL_BACKEND_GTASKS (backend));
+       g_return_if_fail (E_IS_DATA_CAL (cal));
+
+       e_data_cal_respond_discard_alarm (cal, opid, EDC_ERROR (NotSupported));
+}
+
+static void
+ecb_gtasks_start_view (ECalBackend *backend,
+                      EDataCalView *view)
+{
+       ECalBackendGTasks *gtasks;
+       ECalBackendSExp *sexp;
+       ETimezoneCache *cache;
+       const gchar *sexp_str;
+       gboolean do_search;
+       GSList *list, *iter;
+       time_t occur_start = -1, occur_end = -1;
+       gboolean prunning_by_time;
+
+       g_return_if_fail (E_IS_CAL_BACKEND_GTASKS (backend));
+       g_return_if_fail (E_IS_DATA_CAL_VIEW (view));
+
+       g_object_ref (view);
+
+       gtasks = E_CAL_BACKEND_GTASKS (backend);
+       sexp = e_data_cal_view_get_sexp (view);
+       sexp_str = e_cal_backend_sexp_text (sexp);
+       do_search = !g_str_equal (sexp_str, "#t");
+       prunning_by_time = e_cal_backend_sexp_evaluate_occur_times (sexp, &occur_start, &occur_end);
+
+       cache = E_TIMEZONE_CACHE (backend);
+
+       list = prunning_by_time ?
+               e_cal_backend_store_get_components_occuring_in_range (gtasks->priv->store, occur_start, 
occur_end)
+               : e_cal_backend_store_get_components (gtasks->priv->store);
+
+       for (iter = list; iter; iter = g_slist_next (iter)) {
+               ECalComponent *comp = E_CAL_COMPONENT (iter->data);
+
+               if (!do_search || e_cal_backend_sexp_match_comp (sexp, comp, cache)) {
+                       e_data_cal_view_notify_components_added_1 (view, comp);
+               }
+
+               g_object_unref (comp);
+       }
+
+       g_slist_free (list);
+
+       e_data_cal_view_notify_complete (view, NULL /* Success */);
+
+       g_object_unref (view);
+}
+
+static void
+ecb_gtasks_stop_view (ECalBackend *backend,
+                     EDataCalView *view)
+{
+       g_return_if_fail (E_IS_CAL_BACKEND_GTASKS (backend));
+       g_return_if_fail (E_IS_DATA_CAL_VIEW (view));
+}
+
+static void
+ecb_gtasks_shutdown (ECalBackend *backend)
+{
+       ECalBackendGTasks *gtasks;
+
+       g_return_if_fail (E_IS_CAL_BACKEND_GTASKS (backend));
+
+       gtasks = E_CAL_BACKEND_GTASKS (backend);
+
+       ecb_gtasks_take_cancellable (gtasks, NULL);
+
+       if (gtasks->priv->refresh_id) {
+               ESource *source = e_backend_get_source (E_BACKEND (backend));
+               if (source)
+                       e_source_refresh_remove_timeout (source, gtasks->priv->refresh_id);
+
+               gtasks->priv->refresh_id = 0;
+       }
+
+       /* Chain up to parent's method. */
+       E_CAL_BACKEND_CLASS (e_cal_backend_gtasks_parent_class)->shutdown (backend);
+}
+
+static void
+e_cal_backend_gtasks_init (ECalBackendGTasks *gtasks)
+{
+       gtasks->priv = E_CAL_BACKEND_GTASKS_GET_PRIVATE (gtasks);
+       gtasks->priv->cancellable = NULL;
+
+       g_mutex_init (&gtasks->priv->property_mutex);
+}
+
+static void
+ecb_gtasks_constructed (GObject *object)
+{
+       ECalBackendGTasks *gtasks = E_CAL_BACKEND_GTASKS (object);
+       ESource *source;
+
+       /* Chain up to parent's method. */
+       G_OBJECT_CLASS (e_cal_backend_gtasks_parent_class)->constructed (object);
+
+       gtasks->priv->store = e_cal_backend_store_new (
+               e_cal_backend_get_cache_dir (E_CAL_BACKEND (gtasks)),
+               E_TIMEZONE_CACHE (gtasks));
+       e_cal_backend_store_load (gtasks->priv->store);
+
+       source = e_backend_get_source (E_BACKEND (gtasks));
+       gtasks->priv->refresh_id = e_source_refresh_add_timeout (
+               source, NULL, ecb_gtasks_time_to_refresh_data_cb, gtasks, NULL);
+}
+
+static void
+ecb_gtasks_dispose (GObject *object)
+{
+       ECalBackendGTasks *gtasks = E_CAL_BACKEND_GTASKS (object);
+
+       ecb_gtasks_take_cancellable (gtasks, NULL);
+
+       g_clear_object (&gtasks->priv->cancellable);
+       g_clear_object (&gtasks->priv->service);
+       g_clear_object (&gtasks->priv->authorizer);
+       g_clear_object (&gtasks->priv->tasklist);
+       g_clear_object (&gtasks->priv->store);
+
+       if (gtasks->priv->refresh_id) {
+               ESource *source = e_backend_get_source (E_BACKEND (object));
+               if (source)
+                       e_source_refresh_remove_timeout (source, gtasks->priv->refresh_id);
+
+               gtasks->priv->refresh_id = 0;
+       }
+
+       /* Chain up to parent's method. */
+       G_OBJECT_CLASS (e_cal_backend_gtasks_parent_class)->dispose (object);
+}
+
+static void
+ecb_gtasks_finalize (GObject *object)
+{
+       ECalBackendGTasks *gtasks = E_CAL_BACKEND_GTASKS (object);
+
+       g_mutex_clear (&gtasks->priv->property_mutex);
+
+       /* Chain up to parent's method. */
+       G_OBJECT_CLASS (e_cal_backend_gtasks_parent_class)->finalize (object);
+}
+
+static void
+e_cal_backend_gtasks_class_init (ECalBackendGTasksClass *class)
+{
+       GObjectClass *object_class;
+       ECalBackendClass *backend_class;
+
+       object_class = (GObjectClass *) class;
+       backend_class = (ECalBackendClass *) class;
+
+       g_type_class_add_private (class, sizeof (ECalBackendGTasksPrivate));
+
+       object_class->constructed = ecb_gtasks_constructed;
+       object_class->dispose = ecb_gtasks_dispose;
+       object_class->finalize = ecb_gtasks_finalize;
+
+       backend_class->get_backend_property = ecb_gtasks_get_backend_property;
+       backend_class->open = ecb_gtasks_open;
+       backend_class->refresh = ecb_gtasks_refresh;
+       backend_class->get_object = ecb_gtasks_get_object;
+       backend_class->get_object_list = ecb_gtasks_get_object_list;
+       backend_class->get_free_busy = ecb_gtasks_get_free_busy;
+       backend_class->create_objects = ecb_gtasks_create_objects;
+       backend_class->modify_objects = ecb_gtasks_modify_objects;
+       backend_class->remove_objects = ecb_gtasks_remove_objects;
+       backend_class->receive_objects = ecb_gtasks_receive_objects;
+       backend_class->send_objects = ecb_gtasks_send_objects;
+       backend_class->get_attachment_uris = ecb_gtasks_get_attachment_uris;
+       backend_class->discard_alarm = ecb_gtasks_discard_alarm;
+       backend_class->start_view = ecb_gtasks_start_view;
+       backend_class->stop_view = ecb_gtasks_stop_view;
+       backend_class->shutdown = ecb_gtasks_shutdown;
+}
diff --git a/calendar/backends/gtasks/e-cal-backend-gtasks.h b/calendar/backends/gtasks/e-cal-backend-gtasks.h
new file mode 100644
index 0000000..6823a98
--- /dev/null
+++ b/calendar/backends/gtasks/e-cal-backend-gtasks.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2014 Red Hat, Inc. (www.redhat.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Milan Crha <mcrha redhat com>
+ */
+
+#ifndef E_CAL_BACKEND_GTASKS_H
+#define E_CAL_BACKEND_GTASKS_H
+
+#include <libedata-cal/libedata-cal.h>
+
+/* Standard GObject macros */
+#define E_TYPE_CAL_BACKEND_GTASKS \
+       (e_cal_backend_gtasks_get_type ())
+#define E_CAL_BACKEND_GTASKS(obj) \
+       (G_TYPE_CHECK_INSTANCE_CAST \
+       ((obj), E_TYPE_CAL_BACKEND_GTASKS, ECalBackendGTasks))
+#define E_CAL_BACKEND_GTASKS_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_CAST \
+       ((cls), E_TYPE_CAL_BACKEND_GTASKS, ECalBackendGTasksClass))
+#define E_IS_CAL_BACKEND_GTASKS(obj) \
+       (G_TYPE_CHECK_INSTANCE_TYPE \
+       ((obj), E_TYPE_CAL_BACKEND_GTASKS))
+#define E_IS_CAL_BACKEND_GTASKS_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_TYPE \
+       ((cls), E_TYPE_CAL_BACKEND_GTASKS))
+#define E_CAL_BACKEND_GTASKS_GET_CLASS(obj) \
+       (G_TYPE_INSTANCE_GET_CLASS \
+       ((obj), E_TYPE_CAL_BACKEND_GTASKS, ECalBackendGTasksClass))
+
+G_BEGIN_DECLS
+
+typedef struct _ECalBackendGTasks ECalBackendGTasks;
+typedef struct _ECalBackendGTasksClass ECalBackendGTasksClass;
+typedef struct _ECalBackendGTasksPrivate ECalBackendGTasksPrivate;
+
+struct _ECalBackendGTasks {
+       ECalBackend parent;
+       ECalBackendGTasksPrivate *priv;
+};
+
+struct _ECalBackendGTasksClass {
+       ECalBackendClass parent_class;
+};
+
+GType          e_cal_backend_gtasks_get_type   (void);
+
+G_END_DECLS
+
+#endif /* E_CAL_BACKEND_GTASKS_H */
diff --git a/calendar/backends/gtasks/e-gdata-oauth2-authorizer.c 
b/calendar/backends/gtasks/e-gdata-oauth2-authorizer.c
new file mode 100644
index 0000000..d65b934
--- /dev/null
+++ b/calendar/backends/gtasks/e-gdata-oauth2-authorizer.c
@@ -0,0 +1,326 @@
+/*
+ * e-gdata-oauth2-authorizer.c
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "e-gdata-oauth2-authorizer.h"
+
+#define E_GDATA_OAUTH2_AUTHORIZER_GET_PRIVATE(obj) \
+       (G_TYPE_INSTANCE_GET_PRIVATE \
+       ((obj), E_TYPE_GDATA_OAUTH2_AUTHORIZER, EGDataOAuth2AuthorizerPrivate))
+
+struct _EGDataOAuth2AuthorizerPrivate {
+       GWeakRef source;
+
+       /* These members are protected by the global mutex. */
+       gchar *access_token;
+       GHashTable *authorization_domains;
+};
+
+enum {
+       PROP_0,
+       PROP_SOURCE
+};
+
+/* GDataAuthorizer methods must be thread-safe. */
+static GMutex mutex;
+
+/* Forward Declarations */
+static void    e_gdata_oauth2_authorizer_interface_init
+                                       (GDataAuthorizerInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (
+       EGDataOAuth2Authorizer,
+       e_gdata_oauth2_authorizer,
+       G_TYPE_OBJECT,
+       G_IMPLEMENT_INTERFACE (
+               GDATA_TYPE_AUTHORIZER,
+               e_gdata_oauth2_authorizer_interface_init))
+
+static gboolean
+gdata_oauth2_authorizer_is_authorized (GDataAuthorizer *authorizer,
+                                       GDataAuthorizationDomain *domain)
+{
+       EGDataOAuth2AuthorizerPrivate *priv;
+
+       /* This MUST be called with the mutex already locked. */
+
+       if (domain == NULL)
+               return TRUE;
+
+       priv = E_GDATA_OAUTH2_AUTHORIZER_GET_PRIVATE (authorizer);
+
+       return g_hash_table_contains (priv->authorization_domains, domain);
+}
+
+static void
+gdata_oauth2_authorizer_set_source (EGDataOAuth2Authorizer *authorizer,
+                                    ESource *source)
+{
+       g_return_if_fail (E_IS_SOURCE (source));
+
+       g_weak_ref_set (&authorizer->priv->source, source);
+}
+
+static void
+gdata_oauth2_authorizer_set_property (GObject *object,
+                                      guint property_id,
+                                      const GValue *value,
+                                      GParamSpec *pspec)
+{
+       switch (property_id) {
+               case PROP_SOURCE:
+                       gdata_oauth2_authorizer_set_source (
+                               E_GDATA_OAUTH2_AUTHORIZER (object),
+                               g_value_get_object (value));
+                       return;
+       }
+
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+gdata_oauth2_authorizer_get_property (GObject *object,
+                                      guint property_id,
+                                      GValue *value,
+                                      GParamSpec *pspec)
+{
+       switch (property_id) {
+               case PROP_SOURCE:
+                       g_value_take_object (
+                               value,
+                               e_gdata_oauth2_authorizer_ref_source (
+                               E_GDATA_OAUTH2_AUTHORIZER (object)));
+                       return;
+       }
+
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+gdata_oauth2_authorizer_dispose (GObject *object)
+{
+       EGDataOAuth2AuthorizerPrivate *priv;
+
+       priv = E_GDATA_OAUTH2_AUTHORIZER_GET_PRIVATE (object);
+
+       g_weak_ref_set (&priv->source, NULL);
+
+       g_hash_table_remove_all (priv->authorization_domains);
+
+       /* Chain up to parent's dispose() method. */
+       G_OBJECT_CLASS (e_gdata_oauth2_authorizer_parent_class)->
+               dispose (object);
+}
+
+static void
+gdata_oauth2_authorizer_finalize (GObject *object)
+{
+       EGDataOAuth2AuthorizerPrivate *priv;
+
+       priv = E_GDATA_OAUTH2_AUTHORIZER_GET_PRIVATE (object);
+
+       g_free (priv->access_token);
+
+       g_hash_table_destroy (priv->authorization_domains);
+       g_weak_ref_clear (&priv->source);
+
+       /* Chain up to parent's finalize() method. */
+       G_OBJECT_CLASS (e_gdata_oauth2_authorizer_parent_class)->
+               finalize (object);
+}
+
+static void
+gdata_oauth2_authorizer_constructed (GObject *object)
+{
+       EGDataOAuth2AuthorizerPrivate *priv;
+       GType service_type;
+       GList *domains;
+
+       /* Chain up to parent's constructed() method. */
+       G_OBJECT_CLASS (e_gdata_oauth2_authorizer_parent_class)->
+               constructed (object);
+
+       priv = E_GDATA_OAUTH2_AUTHORIZER_GET_PRIVATE (object);
+
+       /* XXX We would need to generalize this to make the class
+        *     reusable for other service types, probably by adding
+        *     a "service-type" constructor property. */
+       service_type = GDATA_TYPE_TASKS_SERVICE;
+       domains = gdata_service_get_authorization_domains (service_type);
+
+       while (domains != NULL) {
+               g_hash_table_add (
+                       priv->authorization_domains,
+                       g_object_ref (domains->data));
+               domains = g_list_delete_link (domains, domains);
+       }
+}
+
+static void
+gdata_oauth2_authorizer_process_request (GDataAuthorizer *authorizer,
+                                         GDataAuthorizationDomain *domain,
+                                         SoupMessage *message)
+{
+       EGDataOAuth2AuthorizerPrivate *priv;
+       gchar *authorization;
+
+       priv = E_GDATA_OAUTH2_AUTHORIZER_GET_PRIVATE (authorizer);
+
+       g_mutex_lock (&mutex);
+
+       if (!gdata_oauth2_authorizer_is_authorized (authorizer, domain))
+               goto exit;
+
+       /* We can't add an Authorization header without an access token.
+        * Let the request fail.  GData should refresh us if it gets back
+        * a "401 Authorization required" response from Google, and then
+        * automatically retry the request. */
+       if (priv->access_token == NULL)
+               goto exit;
+
+       authorization = g_strdup_printf ("OAuth %s", priv->access_token);
+
+       /* Use replace here, not append, to make sure
+        * there's only one "Authorization" header. */
+       soup_message_headers_replace (
+               message->request_headers,
+               "Authorization", authorization);
+
+       g_free (authorization);
+
+exit:
+       g_mutex_unlock (&mutex);
+}
+
+static gboolean
+gdata_oauth2_authorizer_is_authorized_for_domain (GDataAuthorizer *authorizer,
+                                                  GDataAuthorizationDomain *domain)
+{
+       gboolean authorized;
+
+       g_mutex_lock (&mutex);
+
+       authorized = gdata_oauth2_authorizer_is_authorized (authorizer, domain);
+
+       g_mutex_unlock (&mutex);
+
+       return authorized;
+}
+
+static gboolean
+gdata_oauth2_authorizer_refresh_authorization (GDataAuthorizer *authorizer,
+                                               GCancellable *cancellable,
+                                               GError **error)
+{
+       EGDataOAuth2Authorizer *oauth2_authorizer;
+       ESource *source;
+       gchar **ptr_access_token;
+       gboolean success = FALSE;
+
+       oauth2_authorizer = E_GDATA_OAUTH2_AUTHORIZER (authorizer);
+       source = e_gdata_oauth2_authorizer_ref_source (oauth2_authorizer);
+       g_return_val_if_fail (source != NULL, FALSE);
+
+       ptr_access_token = &oauth2_authorizer->priv->access_token;
+
+       g_mutex_lock (&mutex);
+
+       g_free (*ptr_access_token);
+       *ptr_access_token = NULL;
+
+       success = e_source_get_oauth2_access_token_sync (
+               source, cancellable, ptr_access_token, NULL, error);
+
+       g_mutex_unlock (&mutex);
+
+       g_object_unref (source);
+
+       return success;
+}
+
+static void
+e_gdata_oauth2_authorizer_class_init (EGDataOAuth2AuthorizerClass *class)
+{
+       GObjectClass *object_class;
+
+       g_type_class_add_private (
+               class, sizeof (EGDataOAuth2AuthorizerPrivate));
+
+       object_class = G_OBJECT_CLASS (class);
+       object_class->set_property = gdata_oauth2_authorizer_set_property;
+       object_class->get_property = gdata_oauth2_authorizer_get_property;
+       object_class->dispose = gdata_oauth2_authorizer_dispose;
+       object_class->finalize = gdata_oauth2_authorizer_finalize;
+       object_class->constructed = gdata_oauth2_authorizer_constructed;
+
+       g_object_class_install_property (
+               object_class,
+               PROP_SOURCE,
+               g_param_spec_object (
+                       "source",
+                       "Source",
+                       "The data source to authenticate",
+                       E_TYPE_SOURCE,
+                       G_PARAM_READWRITE |
+                       G_PARAM_CONSTRUCT_ONLY |
+                       G_PARAM_STATIC_STRINGS));
+}
+
+static void
+e_gdata_oauth2_authorizer_interface_init (GDataAuthorizerInterface *iface)
+{
+       iface->process_request =
+               gdata_oauth2_authorizer_process_request;
+       iface->is_authorized_for_domain =
+               gdata_oauth2_authorizer_is_authorized_for_domain;
+       iface->refresh_authorization =
+               gdata_oauth2_authorizer_refresh_authorization;
+}
+
+static void
+e_gdata_oauth2_authorizer_init (EGDataOAuth2Authorizer *authorizer)
+{
+       GHashTable *authorization_domains;
+
+       authorization_domains = g_hash_table_new_full (
+               (GHashFunc) g_direct_hash,
+               (GEqualFunc) g_direct_equal,
+               (GDestroyNotify) g_object_unref,
+               (GDestroyNotify) NULL);
+
+       authorizer->priv = E_GDATA_OAUTH2_AUTHORIZER_GET_PRIVATE (authorizer);
+       authorizer->priv->authorization_domains = authorization_domains;
+       g_weak_ref_init (&authorizer->priv->source, NULL);
+}
+
+EGDataOAuth2Authorizer *
+e_gdata_oauth2_authorizer_new (ESource *source)
+{
+       g_return_val_if_fail (E_IS_SOURCE (source), NULL);
+
+       return g_object_new (
+               E_TYPE_GDATA_OAUTH2_AUTHORIZER,
+               "source", source, NULL);
+}
+
+ESource *
+e_gdata_oauth2_authorizer_ref_source (EGDataOAuth2Authorizer *authorizer)
+{
+       g_return_val_if_fail (E_IS_GDATA_OAUTH2_AUTHORIZER (authorizer), NULL);
+
+       return g_weak_ref_get (&authorizer->priv->source);
+}
+
diff --git a/calendar/backends/gtasks/e-gdata-oauth2-authorizer.h 
b/calendar/backends/gtasks/e-gdata-oauth2-authorizer.h
new file mode 100644
index 0000000..ef908f6
--- /dev/null
+++ b/calendar/backends/gtasks/e-gdata-oauth2-authorizer.h
@@ -0,0 +1,69 @@
+/*
+ * e-gdata-oauth2-authorizer.h
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef E_GDATA_OAUTH2_AUTHORIZER_H
+#define E_GDATA_OAUTH2_AUTHORIZER_H
+
+#include <gdata/gdata.h>
+#include <libedataserver/libedataserver.h>
+
+/* Standard GObject macros */
+#define E_TYPE_GDATA_OAUTH2_AUTHORIZER \
+       (e_gdata_oauth2_authorizer_get_type ())
+#define E_GDATA_OAUTH2_AUTHORIZER(obj) \
+       (G_TYPE_CHECK_INSTANCE_CAST \
+       ((obj), E_TYPE_GDATA_OAUTH2_AUTHORIZER, EGDataOAuth2Authorizer))
+#define E_GDATA_OAUTH2_AUTHORIZER_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_CAST \
+       ((cls), E_TYPE_GDATA_OAUTH2_AUTHORIZER, EGDataOAuth2AuthorizerClass))
+#define E_IS_GDATA_OAUTH2_AUTHORIZER(obj) \
+       (G_TYPE_CHECK_INSTANCE_TYPE \
+       ((obj), E_TYPE_GDATA_OAUTH2_AUTHORIZER))
+#define E_IS_GDATA_OAUTH2_AUTHORIZER_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_TYPE \
+       ((cls), E_TYPE_GDATA_OAUTH2_AUTHORIZER))
+#define E_GDATA_OAUTH2_AUTHORIZER_GET_CLASS(obj) \
+       (G_TYPE_INSTANCE_GET_CLASS \
+       ((obj), E_TYPE_GDATA_OAUTH2_AUTHORIZER, EGDataOAuth2AuthorizerClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EGDataOAuth2Authorizer EGDataOAuth2Authorizer;
+typedef struct _EGDataOAuth2AuthorizerClass EGDataOAuth2AuthorizerClass;
+typedef struct _EGDataOAuth2AuthorizerPrivate EGDataOAuth2AuthorizerPrivate;
+
+struct _EGDataOAuth2Authorizer {
+       GObject parent;
+       EGDataOAuth2AuthorizerPrivate *priv;
+};
+
+struct _EGDataOAuth2AuthorizerClass {
+       GObjectClass parent_class;
+};
+
+GType          e_gdata_oauth2_authorizer_get_type
+                                       (void) G_GNUC_CONST;
+EGDataOAuth2Authorizer *
+               e_gdata_oauth2_authorizer_new
+                                       (ESource *source);
+ESource *      e_gdata_oauth2_authorizer_ref_source
+                                       (EGDataOAuth2Authorizer *authorizer);
+
+G_END_DECLS
+
+#endif /* E_GDATA_OAUTH2_AUTHORIZER_H */
+
diff --git a/configure.ac b/configure.ac
index c85fada..6582b24 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1500,6 +1500,7 @@ dnl *****
 dnl ******************************
 dnl Google flags
 dnl ******************************
+have_gdata_0_15_1=no
 LIBGDATA_REQUIRED=libgdata_minimum_version
 AC_SUBST(LIBGDATA_REQUIRED)
 AC_ARG_ENABLE([google],
@@ -1514,8 +1515,13 @@ if test "x$enable_google" = xyes; then
        AC_SUBST(GDATA_LIBS)
 
        AC_DEFINE(HAVE_GOOGLE, 1, [Define to 1 if you have the google-1.0 package.])
+
+       if `$PKG_CONFIG --atleast-version=0.15.1 libgdata`; then
+               have_gdata_0_15_1=yes
+       fi
 fi
 AM_CONDITIONAL(HAVE_GOOGLE, [test x$enable_google = xyes])
+AM_CONDITIONAL(HAVE_GDATA_0_15_1, [test x$have_gdata_0_15_1 = xyes])
 
 EVO_SET_COMPILE_FLAGS(SOUP, libsoup-2.4)
 AC_SUBST(SOUP_CFLAGS)
@@ -1727,6 +1733,7 @@ calendar/libegdbus/Makefile
 calendar/backends/Makefile
 calendar/backends/caldav/Makefile
 calendar/backends/file/Makefile
+calendar/backends/gtasks/Makefile
 calendar/backends/http/Makefile
 calendar/backends/contacts/Makefile
 calendar/backends/weather/Makefile
@@ -1827,6 +1834,7 @@ echo "
        GNOME Online Accounts   $enable_goa
        Ubuntu Online Accounts  $enable_uoa
        Google Contacts         $enable_google
+       Google Tasks            $have_gdata_0_15_1
        GTK+:                   $enable_gtk
        Examples:               $enable_examples
        Code coverage (gcov):   $enable_code_coverage
diff --git a/modules/google-backend/module-google-backend.c b/modules/google-backend/module-google-backend.c
index 0e2911e..4f2a47f 100644
--- a/modules/google-backend/module-google-backend.c
+++ b/modules/google-backend/module-google-backend.c
@@ -59,6 +59,10 @@
 #define GOOGLE_CONTACTS_HOST           "www.google.com"
 #define GOOGLE_CONTACTS_RESOURCE_ID    "Contacts"
 
+/* Tasks Configuration Details */
+#define GOOGLE_TASKS_BACKEND_NAME      "gtasks"
+#define GOOGLE_TASKS_RESOURCE_ID       "Tasks List"
+
 typedef struct _EGoogleBackend EGoogleBackend;
 typedef struct _EGoogleBackendClass EGoogleBackendClass;
 
@@ -242,6 +246,63 @@ google_backend_add_calendar (ECollectionBackend *backend)
 }
 
 static void
+google_backend_add_tasks (ECollectionBackend *backend)
+{
+       ESource *source;
+       ESource *collection_source;
+       ESourceRegistryServer *server;
+       ESourceExtension *extension;
+       ESourceCollection *collection_extension;
+       const gchar *backend_name;
+       const gchar *extension_name;
+       const gchar *resource_id;
+
+       /* FIXME As a future enhancement, we should query Google
+        *       for a list of user calendars and add them to the
+        *       collection with matching display names and colors. */
+
+       collection_source = e_backend_get_source (E_BACKEND (backend));
+
+       resource_id = GOOGLE_TASKS_RESOURCE_ID;
+       source = e_collection_backend_new_child (backend, resource_id);
+       e_source_set_display_name (source, _("Tasks"));
+
+       collection_extension = e_source_get_extension (
+               collection_source, E_SOURCE_EXTENSION_COLLECTION);
+
+       /* Configure the calendar source. */
+
+       backend_name = GOOGLE_TASKS_BACKEND_NAME;
+
+       extension_name = E_SOURCE_EXTENSION_TASK_LIST;
+       extension = e_source_get_extension (source, extension_name);
+
+       e_source_backend_set_backend_name (
+               E_SOURCE_BACKEND (extension), backend_name);
+
+       extension_name = E_SOURCE_EXTENSION_AUTHENTICATION;
+       extension = e_source_get_extension (source, extension_name);
+
+       e_source_authentication_set_host (E_SOURCE_AUTHENTICATION (extension), "www.google.com");
+       e_source_authentication_set_method (E_SOURCE_AUTHENTICATION (extension), "OAuth2");
+
+       g_object_bind_property (
+               collection_extension, "identity",
+               extension, "user",
+               G_BINDING_SYNC_CREATE);
+
+       extension_name = E_SOURCE_EXTENSION_ALARMS;
+       extension = e_source_get_extension (source, extension_name);
+       e_source_alarms_set_include_me (E_SOURCE_ALARMS (extension), FALSE);
+
+       server = e_collection_backend_ref_server (backend);
+       e_source_registry_server_add_source (server, source);
+       g_object_unref (server);
+
+       g_object_unref (source);
+}
+
+static void
 google_backend_add_contacts (ECollectionBackend *backend)
 {
        ESource *source;
@@ -295,13 +356,26 @@ google_backend_add_contacts (ECollectionBackend *backend)
 static void
 google_backend_populate (ECollectionBackend *backend)
 {
-       GList *list;
+       GList *list, *link;
+       gboolean have_calendar = FALSE, have_tasks = FALSE;
 
        list = e_collection_backend_list_calendar_sources (backend);
-       if (list == NULL)
-               google_backend_add_calendar (backend);
+       for (link = list; link; link = g_list_next (link)) {
+               ESource *source = link->data;
+
+               have_calendar = have_calendar || e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR);
+               have_tasks = have_tasks || e_source_has_extension (source, E_SOURCE_EXTENSION_TASK_LIST);
+
+               if (have_calendar && have_tasks)
+                       break;
+       }
        g_list_free_full (list, (GDestroyNotify) g_object_unref);
 
+       if (!have_calendar)
+               google_backend_add_calendar (backend);
+       if (!have_tasks)
+               google_backend_add_tasks (backend);
+
        list = e_collection_backend_list_contacts_sources (backend);
        if (list == NULL)
                google_backend_add_contacts (backend);
@@ -325,6 +399,10 @@ google_backend_dup_resource_id (ECollectionBackend *backend,
        if (e_source_has_extension (child_source, extension_name))
                return g_strdup (GOOGLE_CALENDAR_RESOURCE_ID);
 
+       extension_name = E_SOURCE_EXTENSION_TASK_LIST;
+       if (e_source_has_extension (child_source, extension_name))
+               return g_strdup (GOOGLE_TASKS_RESOURCE_ID);
+
        extension_name = E_SOURCE_EXTENSION_ADDRESS_BOOK;
        if (e_source_has_extension (child_source, extension_name))
                return g_strdup (GOOGLE_CONTACTS_RESOURCE_ID);


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