diff options
| author | Milan Crha <mcrha@redhat.com> | 2016-10-11 11:47:14 +0200 |
|---|---|---|
| committer | Milan Crha <mcrha@redhat.com> | 2016-10-11 11:47:14 +0200 |
| commit | d7931c6dd9db1e090f4bb466983c3dced19e2201 (patch) | |
| tree | 31e31eef195355e800c63be6b4dcfefe6e37bb84 /src/calendar/backends/gtasks | |
| parent | 4febe3ae82e850ca9f17229dd2dbd9cdd8708a8f (diff) | |
| download | evolution-data-server-d7931c6dd9db1e090f4bb466983c3dced19e2201.tar.gz | |
Reorganize directory structure
Let's have it as it's common to be, which means top level src/ for
sources, single data/ for data, and so on.
Diffstat (limited to 'src/calendar/backends/gtasks')
| -rw-r--r-- | src/calendar/backends/gtasks/CMakeLists.txt | 52 | ||||
| -rw-r--r-- | src/calendar/backends/gtasks/e-cal-backend-gtasks-factory.c | 78 | ||||
| -rw-r--r-- | src/calendar/backends/gtasks/e-cal-backend-gtasks.c | 1553 | ||||
| -rw-r--r-- | src/calendar/backends/gtasks/e-cal-backend-gtasks.h | 62 | ||||
| -rw-r--r-- | src/calendar/backends/gtasks/e-gdata-oauth2-authorizer.c | 361 | ||||
| -rw-r--r-- | src/calendar/backends/gtasks/e-gdata-oauth2-authorizer.h | 75 |
6 files changed, 2181 insertions, 0 deletions
diff --git a/src/calendar/backends/gtasks/CMakeLists.txt b/src/calendar/backends/gtasks/CMakeLists.txt new file mode 100644 index 000000000..38ee19a98 --- /dev/null +++ b/src/calendar/backends/gtasks/CMakeLists.txt @@ -0,0 +1,52 @@ +set(DEPENDENCIES + ebackend + ecal + edataserver + edata-cal +) + +set(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 +) + +add_library(ecalbackendgtasks MODULE + ${SOURCES} +) + +add_dependencies(ecalbackendgtasks + ${DEPENDENCIES} +) + +target_compile_definitions(ecalbackendgtasks PRIVATE + -DG_LOG_DOMAIN=\"e-cal-backend-gtasks\" + -DBACKENDDIR=\"${ecal_backenddir}\" +) + +target_compile_options(ecalbackendgtasks PUBLIC + ${CALENDAR_CFLAGS} + ${LIBGDATA_CFLAGS} +) + +target_include_directories(ecalbackendgtasks PUBLIC + ${CMAKE_BINARY_DIR} + ${CMAKE_BINARY_DIR}/src + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_BINARY_DIR}/src/calendar + ${CMAKE_SOURCE_DIR}/src/calendar + ${CALENDAR_INCLUDE_DIRS} + ${LIBGDATA_INCLUDE_DIRS} +) + +target_link_libraries(ecalbackendgtasks + ${DEPENDENCIES} + ${CALENDAR_LDFLAGS} + ${LIBGDATA_LDFLAGS} +) + +install(TARGETS ecalbackendgtasks + DESTINATION ${ecal_backenddir} +) diff --git a/src/calendar/backends/gtasks/e-cal-backend-gtasks-factory.c b/src/calendar/backends/gtasks/e-cal-backend-gtasks-factory.c new file mode 100644 index 000000000..f7f5ed0e9 --- /dev/null +++ b/src/calendar/backends/gtasks/e-cal-backend-gtasks-factory.c @@ -0,0 +1,78 @@ +/* + * 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 "evolution-data-server-config.h" + +#include "e-cal-backend-gtasks.h" + +#define FACTORY_NAME "gtasks" + +typedef ECalBackendFactory ECalBackendGTasksFactory; +typedef ECalBackendFactoryClass ECalBackendGTasksFactoryClass; + +static EModule *e_module; + +/* 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) +{ + EBackendFactoryClass *backend_factory_class; + + backend_factory_class = E_BACKEND_FACTORY_CLASS (class); + backend_factory_class->e_module = e_module; + backend_factory_class->share_subprocess = TRUE; + + 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_module = E_MODULE (type_module); + + e_cal_backend_gtasks_factory_register_type (type_module); +} + +G_MODULE_EXPORT void +e_module_unload (GTypeModule *type_module) +{ + e_module = NULL; +} diff --git a/src/calendar/backends/gtasks/e-cal-backend-gtasks.c b/src/calendar/backends/gtasks/e-cal-backend-gtasks.c new file mode 100644 index 000000000..2a3f35fb4 --- /dev/null +++ b/src/calendar/backends/gtasks/e-cal-backend-gtasks.c @@ -0,0 +1,1553 @@ +/* + * 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 "evolution-data-server-config.h" + +#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 || + !gtasks->priv->tasklist) + 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; + GDataFeed *feed; + GDataQuery *query; + gchar *id; + + g_return_if_fail (E_IS_CAL_BACKEND_GTASKS (gtasks)); + g_return_if_fail (gtasks->priv->service != NULL); + g_return_if_fail (gdata_service_is_authorized (GDATA_SERVICE (gtasks->priv->service))); + + 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); + + query = gdata_query_new_with_limits (NULL, 0, 1); + /* This also verifies that the service can connect to the server with given credentials */ + feed = gdata_tasks_service_query_all_tasklists (gtasks->priv->service, query, cancellable, NULL, NULL, error); + if (feed) { + /* If the tasklist ID is not set, then pick the first from the list, most likely the "Default List" */ + if (!id || !*id) { + 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 (>asks->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, + ECalComponent *cached_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) { + g_free (tmp); + tmp = NULL; + + /* If the passed-in component doesn't contain the libgdata self link, + then get it from the cached comp */ + if (cached_comp) { + tmp = ecb_gtasks_icomp_x_prop_get ( + e_cal_component_get_icalcomponent (cached_comp), + 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 (>asks); + 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 (!local_error) + e_backend_ensure_source_status_connected (E_BACKEND (gtasks)); + + 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 (>asks); + 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 (!local_error) + e_backend_ensure_source_status_connected (E_BACKEND (gtasks)); + + 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, + const ENamedParameters *credentials, + 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; + EGDataOAuth2Authorizer *authorizer; + + source = e_backend_get_source (E_BACKEND (backend)); + + /* Only OAuth2 is supported with Google Tasks */ + authorizer = e_gdata_oauth2_authorizer_new (source); + gtasks->priv->authorizer = GDATA_AUTHORIZER (authorizer); + } + + if (E_IS_GDATA_OAUTH2_AUTHORIZER (gtasks->priv->authorizer)) { + e_gdata_oauth2_authorizer_set_credentials (E_GDATA_OAUTH2_AUTHORIZER (gtasks->priv->authorizer), credentials); + } + + if (!gtasks->priv->service) { + GDataTasksService *tasks_service; + + tasks_service = gdata_tasks_service_new (gtasks->priv->authorizer); + gtasks->priv->service = tasks_service; + + e_binding_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, but + there is currently no way to do it, thus simply fail. */ + return FALSE; +} + +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_update_connection_sync (ECalBackendGTasks *gtasks, + const ENamedParameters *credentials, + GCancellable *cancellable, + GError **error) +{ + ECalBackend *backend; + gboolean success; + GError *local_error = NULL; + + g_return_if_fail (E_IS_CAL_BACKEND_GTASKS (gtasks)); + + backend = E_CAL_BACKEND (gtasks); + + success = ecb_gtasks_request_authorization (backend, credentials, 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); + } else { + e_cal_backend_set_writable (backend, FALSE); + } + + if (local_error) + g_propagate_error (error, local_error); +} + +static ESourceAuthenticationResult +ecb_gtasks_authenticate_sync (EBackend *backend, + const ENamedParameters *credentials, + gchar **out_certificate_pem, + GTlsCertificateFlags *out_certificate_errors, + GCancellable *cancellable, + GError **error) +{ + ECalBackendGTasks *gtasks; + ESourceAuthenticationResult result; + GError *local_error = NULL; + + gtasks = E_CAL_BACKEND_GTASKS (backend); + + ecb_gtasks_update_connection_sync (gtasks, credentials, cancellable, &local_error); + + if (local_error == NULL) { + result = E_SOURCE_AUTHENTICATION_ACCEPTED; + + } else if (g_error_matches (local_error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED)) { + if (!e_named_parameters_get (credentials, E_SOURCE_CREDENTIAL_PASSWORD)) + result = E_SOURCE_AUTHENTICATION_REQUIRED; + else + result = E_SOURCE_AUTHENTICATION_REJECTED; + g_clear_error (&local_error); + } else { + result = E_SOURCE_AUTHENTICATION_ERROR; + g_propagate_error (error, local_error); + } + + return result; +} + +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))) { + ESource *source; + gchar *auth_method = NULL; + + source = e_backend_get_source (E_BACKEND (backend)); + + if (e_source_has_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION)) { + ESourceAuthentication *auth_extension; + + auth_extension = e_source_get_extension (source, E_SOURCE_EXTENSION_AUTHENTICATION); + auth_method = e_source_authentication_dup_method (auth_extension); + } + + if (g_strcmp0 (auth_method, "Google") == 0) { + e_backend_credentials_required_sync ( + E_BACKEND (backend), E_SOURCE_CREDENTIALS_REASON_REQUIRED, + NULL, 0, NULL, cancellable, &local_error); + } else { + ecb_gtasks_update_connection_sync (gtasks, NULL, cancellable, &local_error); + } + + g_free (auth_method); + } + + 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), NULL); +} + +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, NULL); + 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, cached_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 (!local_error) + e_backend_ensure_source_status_connected (E_BACKEND (backend)); + + 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, NULL); + 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; + } + + if (!local_error) + e_backend_ensure_source_status_connected (E_BACKEND (backend)); + + 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_add_timezone (ECalBackend *backend, + EDataCal *cal, + guint32 opid, + GCancellable *cancellable, + const gchar *tzobject) +{ + /* Nothing to do, times are in UTC */ + e_data_cal_respond_add_timezone (cal, opid, NULL); +} + +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 (>asks->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 (>asks->priv->cancellable); + g_clear_object (>asks->priv->service); + g_clear_object (>asks->priv->authorizer); + g_clear_object (>asks->priv->tasklist); + g_clear_object (>asks->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 (>asks->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; + EBackendClass *backend_class; + ECalBackendClass *cal_backend_class; + + g_type_class_add_private (class, sizeof (ECalBackendGTasksPrivate)); + + object_class = (GObjectClass *) class; + object_class->constructed = ecb_gtasks_constructed; + object_class->dispose = ecb_gtasks_dispose; + object_class->finalize = ecb_gtasks_finalize; + + backend_class = (EBackendClass *) class; + backend_class->authenticate_sync = ecb_gtasks_authenticate_sync; + + cal_backend_class = (ECalBackendClass *) class; + cal_backend_class->get_backend_property = ecb_gtasks_get_backend_property; + cal_backend_class->open = ecb_gtasks_open; + cal_backend_class->refresh = ecb_gtasks_refresh; + cal_backend_class->get_object = ecb_gtasks_get_object; + cal_backend_class->get_object_list = ecb_gtasks_get_object_list; + cal_backend_class->get_free_busy = ecb_gtasks_get_free_busy; + cal_backend_class->create_objects = ecb_gtasks_create_objects; + cal_backend_class->modify_objects = ecb_gtasks_modify_objects; + cal_backend_class->remove_objects = ecb_gtasks_remove_objects; + cal_backend_class->receive_objects = ecb_gtasks_receive_objects; + cal_backend_class->send_objects = ecb_gtasks_send_objects; + cal_backend_class->get_attachment_uris = ecb_gtasks_get_attachment_uris; + cal_backend_class->discard_alarm = ecb_gtasks_discard_alarm; + cal_backend_class->start_view = ecb_gtasks_start_view; + cal_backend_class->stop_view = ecb_gtasks_stop_view; + cal_backend_class->add_timezone = ecb_gtasks_add_timezone; + cal_backend_class->shutdown = ecb_gtasks_shutdown; +} diff --git a/src/calendar/backends/gtasks/e-cal-backend-gtasks.h b/src/calendar/backends/gtasks/e-cal-backend-gtasks.h new file mode 100644 index 000000000..6823a98e0 --- /dev/null +++ b/src/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/src/calendar/backends/gtasks/e-gdata-oauth2-authorizer.c b/src/calendar/backends/gtasks/e-gdata-oauth2-authorizer.c new file mode 100644 index 000000000..094b30bb3 --- /dev/null +++ b/src/calendar/backends/gtasks/e-gdata-oauth2-authorizer.c @@ -0,0 +1,361 @@ +/* + * 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; + ENamedParameters *credentials; +}; + +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); + + e_named_parameters_free (priv->credentials); + + /* 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_util_get_source_oauth2_access_token_sync (source, oauth2_authorizer->priv->credentials, + ptr_access_token, NULL, cancellable, 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); +} + +void +e_gdata_oauth2_authorizer_set_credentials (EGDataOAuth2Authorizer *authorizer, + const ENamedParameters *credentials) +{ + g_return_if_fail (E_IS_GDATA_OAUTH2_AUTHORIZER (authorizer)); + + g_mutex_lock (&mutex); + + e_named_parameters_free (authorizer->priv->credentials); + if (credentials) + authorizer->priv->credentials = e_named_parameters_new_clone (credentials); + else + authorizer->priv->credentials = NULL; + + g_mutex_unlock (&mutex); +} + +ENamedParameters * +e_gdata_oauth2_authorizer_clone_credentials (EGDataOAuth2Authorizer *authorizer) +{ + ENamedParameters *credentials = NULL; + + g_return_val_if_fail (E_IS_GDATA_OAUTH2_AUTHORIZER (authorizer), NULL); + + g_mutex_lock (&mutex); + + if (authorizer->priv->credentials) + credentials = e_named_parameters_new_clone (authorizer->priv->credentials); + + g_mutex_unlock (&mutex); + + return credentials; +} diff --git a/src/calendar/backends/gtasks/e-gdata-oauth2-authorizer.h b/src/calendar/backends/gtasks/e-gdata-oauth2-authorizer.h new file mode 100644 index 000000000..0dfc582f5 --- /dev/null +++ b/src/calendar/backends/gtasks/e-gdata-oauth2-authorizer.h @@ -0,0 +1,75 @@ +/* + * 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); +void e_gdata_oauth2_authorizer_set_credentials + (EGDataOAuth2Authorizer *authorizer, + const ENamedParameters *credentials); +ENamedParameters * + e_gdata_oauth2_authorizer_clone_credentials + (EGDataOAuth2Authorizer *authorizer); + +G_END_DECLS + +#endif /* E_GDATA_OAUTH2_AUTHORIZER_H */ + |
